Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - Initialise model object with init(from decoder:)

Below is my model struct

struct MovieResponse: Codable {
    
    var totalResults: Int
    var response: String
    var error: String
    var movies: [Movie]
    
    enum ConfigKeys: String, CodingKey {
        case totalResults
        case response = "Response"
        case error = "Error"
        case movies
    }
    
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults)!
        self.response = try values.decodeIfPresent(String.self, forKey: .response)!
        self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
        self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies)!
    }
}

extension MovieResponse {
    struct Movie: Codable, Identifiable {
        var id = UUID()
        var title: String
        var year: Int8
        var imdbID: String
        var type: String
        var poster: URL
        
        enum EncodingKeys: String, CodingKey {
            case title = "Title"
            case year = "Year"
            case imdmID
            case type = "Type"
            case poster = "Poster"
        }
    }
}

Now in a ViewModel, I am creating an instance of this model using the below code

@Published var movieObj = MovieResponse()

But there is a compile error saying, call init(from decoder) method. What is the proper way to create a model instance in this case?

like image 551
aios Avatar asked Oct 30 '25 08:10

aios


1 Answers

As the Swift Language Guide reads:

Swift provides a default initializer for any structure or class that provides default values for all of its properties and doesn’t provide at least one initializer itself.

The "and doesn’t provide at least one initializer itself" part is crucial here. Since you are declaring an additional initializer you should either declare your own initialized like so:

init(
    totalResults: Int,
    response: String,
    error: String,
    movies: [Movie]
) {
    self.totalResults = totalResults
    self.response = response
    self.error = error
    self.movies = movies
}

or move Codable conformance to an extension so Swift can provide you with a default initialiser. This would be a preferred way to do it (my personal opinion, I like to move additional protocol conformances to extensions).

struct MovieResponse {
    var totalResults: Int
    var response: String
    var error: String
    var movies: [Movie]
}

extension MovieResponse: Codable {

    enum ConfigKeys: String, CodingKey {
        case totalResults
        case response = "Response"
        case error = "Error"
        case movies
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        self.totalResults = try values.decodeIfPresent(Int.self, forKey: .totalResults)!
        self.response = try values.decodeIfPresent(String.self, forKey: .response)!
        self.error = try values.decodeIfPresent(String.self, forKey: .error) ?? ""
        self.movies = try values.decodeIfPresent([Movie].self, forKey: .movies)!
    }
}
like image 57
Witek Bobrowski Avatar answered Nov 02 '25 00:11

Witek Bobrowski