web-dev-qa-db-fra.com

Swift Init codable

Je voudrais faire une logique d'initialisation après que la fonction Swift Coding/Encoding a fini de décoder un JSON.

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

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

Mais j'obtiens l'erreur du compilateur:

Return from initializer without initializing all stored properties

Ce qui est clair pour moi car init () veut que j'initialise toutes les propriétés. Mais l'ajout d'un init () avec toutes les propriétés nécessaires ne le résout pas non plus car cet initialiseur n'est pas appelé (!) Lorsque Codable entre en action:

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

Néanmoins - existe-t-il un moyen de faire une logique d'initialisation après la fin du décodage, mais sans faire tout le décodage manuellement pour chaque propriété? Donc sans implémenter à chaque fois init(from decoder: Decoder). Dans ce court exemple, je n'ai que deux propriétés simples, mais le code de production se compose de milliers d'entre elles.

Merci.

6
Darko

Soit vous obtenez tout gratuitement mais standardisé ou vous devez écrire un initialiseur personnalisé comme

struct MyStruct: Codable  {

    let id: Int 
    var name: String

    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)" 
    }
}

Vous pouvez implémenter init() mais cela fonctionne indépendamment de la fonctionnalité de décodage et vous devez attribuer une valeur par défaut à toutes les propriétés non facultatives, c'est ce que l'erreur indique.

13
vadian

Utilisez une méthode d'usine qui utilise d'abord init(from:) puis appelle votre code d'initialisation personnalisé

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)
4
Gereon