Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Codable init

I would like to do some initialization logic after the Swift Coding/Encoding feature has finished decoding a JSON.

struct MyStruct: Codable {
    let id: Int 
    var name: String

    init() {
       name = "\(id) \(name)" 
    }
}

But I get the compiler error:

Return from initializer without initializing all stored properties

Which is clear to me because init() wants me to initialise all properties. But adding an init() with all needed properties also doesn't solve it because this initializer is not called(!) when Codable kicks in:

init(id: Int, name: String) {
    // This initializer is not called if Decoded from JSON!
    self.id = id 
    self.name = "\(id) \(name)" 
}

Nevertheless - is there a way to do some initialisation logic after the Decoding has finished but without doing all the decoding manually for each property? So without implementing every time init(from decoder: Decoder). In this short example I have just two simple properties but production code consists of thousands of them.

Thanks.

like image 919
Darko Avatar asked Jan 16 '18 11:01

Darko


People also ask

What does Codable mean in Swift?

Codable is the combined protocol of Swift's Decodable and Encodable protocols. Together they provide standard methods of decoding data for custom types and encoding data to be saved or transferred.

What types are Codable Swift?

There are many types in Swift that are codable out of the box: Int , String , Date , Array and many other types from the Standard Library and the Foundation framework. If you want your type to be codable, the simplest way to do it is by conforming to Codable and making sure all its stored properties are also codable.

What is the difference between Codable and Decodable in Swift?

Understanding what Swift's Codable isWhen you only want to convert JSON data into a struct, you can conform your object to Decodable . If you only want to transform instances of your struct into Data , you can conform your object to Encodable , and if you want to do both you can conform to Codable .


2 Answers

Either you get everything for free but standardized or you have to write a custom initializer like

struct MyStruct: Codable  {

    let id: Int 
    var name: String

    private enum CodingKeys : String, CodingKey { case id, name }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(Int.self, forKey: .id)
        let decodedName = try container.decode(String.self, forKey: .name)
        name = "\(id) \(decodedName)" 
    }
}

You can implement init() but this works independent of the decoding functionality and you have to assign a default value to all non-optional properties, that's what the error says.

like image 200
vadian Avatar answered Sep 21 '22 16:09

vadian


Use a factory method that first uses init(from:) and then calls your custom initialization code

struct Foo: Decodable {
    let name: String
    let id: Int

    var x: String!

    private mutating func finalizeInit() {
        self.x = "\(name) \(id)"
    }

    static func createFromJSON(_ data: Data) -> Foo? {
        guard var f = try? JSONDecoder().decode(Foo.self, from: data) else { 
            return nil 
        }
        f.finalizeInit()
        return f
    }
}

let sampleData = """
    { "name": "foo", "id": 42 }
    """.data(using: .utf8)!
let f = Foo.createFromJSON(sampleData)
like image 29
Gereon Avatar answered Sep 24 '22 16:09

Gereon