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
}