web-dev-qa-db-fra.com

Utilisation de Decodable dans Swift 4 avec héritage

L'utilisation de l'héritage de classe doit-elle annuler la décodabilité de la classe Par exemple, le code suivant 

class Server : Codable {
    var id : Int?
}

class Development : Server {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil") here

la sortie est: 

1
name is nil

Maintenant, si j'inverse cela, name décode mais id pas.

class Server {
    var id : Int?
}

class Development : Server, Codable {
    var name : String?
    var userId : Int?
}

var json = "{\"id\" : 1,\"name\" : \"Large Building Development\"}"
let jsonDecoder = JSONDecoder()
let item = try jsonDecoder.decode(Development.self, from:json.data(using: .utf8)!) as Development

print(item.id ?? "id is nil")
print(item.name ?? "name is nil")

la sortie est:

id is nil
Large Building Development

Et vous ne pouvez pas exprimer Codable dans les deux classes. 

43
Kevin McQuown

Je crois qu'en cas d'héritage, vous devez implémenter vous-même Coding. Autrement dit, vous devez spécifier CodingKeys et implémenter init(from:) et encode(to:) à la fois dans la superclasse et la sous-classe. Selon la vidéo WWDC (vers 49:28, photo ci-dessous), vous devez appeler super avec le super codeur/décodeur.

 WWDC 2017 Session 212 Screenshot at 49:28 (Source Code)

required init(from decoder: Decoder) throws {

  // Get our container for this subclass' coding keys
  let container = try decoder.container(keyedBy: CodingKeys.self)
  myVar = try container.decode(MyType.self, forKey: .myVar)
  // otherVar = ...

  // Get superDecoder for superclass and call super.init(from:) with it
  let superDecoder = try container.superDecoder()
  try super.init(from: superDecoder)

}

La vidéo semble ne pas montrer le côté encodage (mais c'est container.superEncoder() pour le côté encode(to:)), mais cela fonctionne de la même manière dans votre implémentation encode(to:). Je peux confirmer que cela fonctionne dans ce cas simple (voir le code de terrain de jeu ci-dessous). 

Je suis toujours aux prises avec un comportement étrange moi-même avec un modèle beaucoup plus complexe que je convertis à partir de NSCoding, qui contient de nombreux types nouvellement imbriqués (dont struct et enum) qui présentent ce comportement inattendu nil et "ne devraient pas être". . Sachez simplement qu'il peut y avoir des cas Edge impliquant des types imbriqués. 

Edit: Les types imbriqués semblent bien fonctionner dans ma zone de test; Je soupçonne maintenant quelque chose qui ne va pas avec les classes auto-référencées (pensez aux enfants des noeuds d'arborescence) avec une collection d'elle-même qui contient également des instances des différentes sous-classes de cette classe. Un test d'une classe simple à auto-référencement décode parfaitement (c'est-à-dire sans sous-classes). Je concentre donc maintenant mes efforts sur les raisons de l'échec de la casse des sous-classes. 

Mise à jour du 25 juin 17: J'ai fini par déposer un bogue avec Apple à ce sujet. rdar: // 32911973 - Malheureusement, un cycle d'encodage/décodage d'un tableau de Superclass contenant des éléments Subclass: Superclass entraîne le décodage de tous les éléments du tableau en tant que Superclass (la sous-classe 'init(from:) n'est jamais appelée, ce qui entraîne une perte de données ou pire).

//: Fully-Implemented Inheritance

class FullSuper: Codable {

    var id: UUID?

    init() {}

    private enum CodingKeys: String, CodingKey { case id }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decode(UUID.self, forKey: .id)

    }

    func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)

    }

}

class FullSub: FullSuper {

    var string: String?
    private enum CodingKeys: String, CodingKey { case string }

    override init() { super.init() }

    required init(from decoder: Decoder) throws {

        let container = try decoder.container(keyedBy: CodingKeys.self)
        let superdecoder = try container.superDecoder()
        try super.init(from: superdecoder)

        string = try container.decode(String.self, forKey: .string)

    }

    override func encode(to encoder: Encoder) throws {

        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(string, forKey: .string)

        let superdecoder = container.superEncoder()
        try super.encode(to: superdecoder)

    }
}

let fullSub = FullSub()
fullSub.id = UUID()
fullSub.string = "FullSub"

let fullEncoder = PropertyListEncoder()
let fullData = try fullEncoder.encode(fullSub)

let fullDecoder = PropertyListDecoder()
let fullSubDecoded: FullSub = try fullDecoder.decode(FullSub.self, from: fullData)

Les propriétés de la super-classe et de la sous-classe sont restaurées dans fullSubDecoded.

47
Joshua Nozzi

Found This Link - Allez à la section héritage

override func encode(to encoder: Encoder) throws {
    try super.encode(to: encoder)
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(employeeID, forKey: .employeeID)
}

Pour décoder j'ai fait ceci:

 required init(from decoder: Decoder) throws {

    try super.init(from: decoder)

    let values = try decoder.container(keyedBy: CodingKeys.self)
    total = try values.decode(Int.self, forKey: .total)
  }

private enum CodingKeys: String, CodingKey
{
    case total

}
5
devjme

Que diriez-vous d'utiliser la manière suivante?

protocol Parent: Codable {
    var inheritedProp: Int? {get set}
}

struct Child: Parent {
    var inheritedProp: Int?
    var title: String?

    enum CodingKeys: String, CodingKey {
        case inheritedProp = "inherited_prop"
        case title = "short_title"
    }
}

Informations supplémentaires sur la composition: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/

4
Nav

J'ai pu le faire fonctionner en rendant ma classe de base et mes sous-classes conformes à Decodable au lieu de Codable. Si j'utilisais Codable, cela planterait de manière étrange, par exemple en obtenant un EXC_BAD_ACCESS lors de l'accès à un champ de la sous-classe, mais le débogueur pourrait afficher toutes les valeurs de la sous-classe sans problème.

De plus, transmettre le superDécodeur à la classe de base dans super.init() n'a pas fonctionné. Je viens de passer le décodeur de la sous-classe à la classe de base.

4
ShackBurger

Voici une bibliothèque TypePreservingCodingAdapter pour faire exactement cela (peut être installé avec Cocoapods ou SwiftPackageManager).

Le code ci-dessous est compilé et fonctionne parfaitement avec Swift 4.2. Malheureusement, pour chaque sous-classe, vous devrez implémenter vous-même le codage et le décodage.

import TypePreservingCodingAdapter
import Foundation

// redeclared your types with initializers
class Server: Codable {
    var id: Int?

    init(id: Int?) {
        self.id = id
    }
}

class Development: Server {
    var name: String?
    var userId: Int?

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

    init(id: Int?, name: String?, userId: Int?) {
        self.name = name
        self.userId = userId
        super.init(id: id)
    }

    required init(from decoder: Decoder) throws {
        try super.init(from: decoder)
        let container = try decoder.container(keyedBy: CodingKeys.self)

        name = try container.decodeIfPresent(String.self, forKey: .name)
        userId = try container.decodeIfPresent(Int.self, forKey: .userId)
    }

    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(userId, forKey: .userId)
    }

}

// create and adapter
let adapter = TypePreservingCodingAdapter()
let encoder = JSONEncoder()
let decoder = JSONDecoder()

// inject it into encoder and decoder
encoder.userInfo[.typePreservingAdapter] = adapter
decoder.userInfo[.typePreservingAdapter] = adapter

// register your types with adapter
adapter.register(type: Server.self).register(type: Development.self)


let server = Server(id: 1)
let development = Development(id: 2, name: "dev", userId: 42)

let servers: [Server] = [server, development]

// wrap specific object with Wrap helper object
let data = try! encoder.encode(servers.map { Wrap(wrapped: $0) })

// decode object back and unwrap them force casting to a common ancestor type
let decodedServers = try! decoder.decode([Wrap].self, from: data).map { $0.wrapped as! Server }

// check that decoded object are of correct types
print(decodedServers.first is Server)     // prints true
print(decodedServers.last is Development) // prints true
1
Igor Muzyka