keys jsonserialization jsondecoder coding swift swift4 codable

jsonserialization - string to json swift 4



Uso de Decodable en Swift 4 con herencia (5)

¿Qué tal usar la siguiente manera?

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

Información adicional sobre la composición: http://mikebuss.com/2016/01/10/interfaces-vs-inheritance/

En caso de que el uso de la herencia de clase rompa la decodificación de la clase. Por ejemplo, el siguiente código

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 salida es:

1 name is nil

Ahora si revierto esto, el nombre decodifica pero la identificación no.

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 salida es:

id is nil Large Building Development

Y no puedes expresar Codificable en ambas clases.


Aquí hay una biblioteca TypePreservingCodingAdapter para hacer exactamente eso (se puede instalar con Cocoapods o SwiftPackageManager).

El siguiente código compila y funciona bien con Swift 4.2 . Desafortunadamente para cada subclase, deberá implementar la codificación y decodificación de propiedades por su cuenta.

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


Creo que en el caso de la herencia debe implementar la Coding usted mismo. Es decir, debe especificar CodingKeys e implementar init(from:) y encode(to:) tanto en la superclase como en la subclase. Según el video WWDC (alrededor de 49:28, en la foto a continuación), debe llamar a super con el super codificador / decodificador.

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

El video parece no mostrar el lado de codificación (pero es container.superEncoder() para la encode(to:) lado encode(to:) ) pero funciona de la misma manera en su implementación de encode(to:) . Puedo confirmar que esto funciona en este caso simple (ver el código del área de juegos a continuación).

Todavía estoy luchando con algún comportamiento extraño con un modelo mucho más complejo que estoy convirtiendo de NSCoding , que tiene muchos tipos recién anidados (incluidos struct y enum ) que exhiben este comportamiento nil inesperado y "no deberían ser" . Solo tenga en cuenta que puede haber casos extremos que involucren tipos anidados.

Editar: los tipos anidados parecen funcionar bien en mi patio de pruebas; Ahora sospecho que algo anda mal con las clases autorreferenciadas (piense en hijos de nodos de árbol) con una colección de sí mismo que también contiene instancias de varias subclases de esa clase. Una prueba de una clase de autorreferencia simple decodifica bien (es decir, sin subclases), así que ahora estoy centrando mis esfuerzos en por qué falla el caso de las subclases.

Actualización del 25 de junio de 2017: terminé presentando un error con Apple sobre esto. rdar: // 32911973 - Desafortunadamente, un ciclo de codificación / decodificación de una matriz de Superclass que contiene Subclass: Superclass elementos de Subclass: Superclass darán como resultado que todos los elementos de la matriz se decodifiquen como Superclass (nunca se llama a init(from:) subclase init(from:) , lo que resulta en pérdida de datos o peor).

//: 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 superencoder = container.superEncoder() try super.encode(to: superencoder) } } 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)

Las propiedades de fullSubDecoded y subclase se restauran en fullSubDecoded .


Pude hacer que funcione haciendo que mi clase base y subclases se ajusten a Decodable lugar de Codable . Si usara Codable , se bloquearía de manera extraña, como obtener un EXC_BAD_ACCESS al acceder a un campo de la subclase, pero el depurador podría mostrar todos los valores de la subclase sin ningún problema.

Además, pasar el superdecodificador a la clase base en super.init() no funcionó. Acabo de pasar el decodificador de la subclase a la clase base.


Encontró este enlace: vaya a la sección de herencia

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

Para decodificar hice esto:

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 }