keys coding json swift deserialization swift4 codable

coding - json decodable swift 4



Swift 4 Codable; Cómo descifrar un objeto con una sola clave de nivel de raíz (4)

Estoy usando el protocolo Swift 4 Codable con datos JSON. Mis datos están formateados de tal manera que hay una sola clave en el nivel raíz con un valor de objeto que contiene las propiedades que necesito, como:

{ "user": { "id": 1, "username": "jdoe" } }

Tengo una estructura de User que puede decodificar la clave de user :

struct User: Codable { let id: Int let username: String }

Dado que id y username son propiedades del user , no en el nivel de raíz, tuve que hacer un tipo de envoltorio así:

struct UserWrapper: Codable { let user: User }

Luego puedo decodificar el JSON a través del UserWrapper , y el User también se decodifica. Parece un montón de código redundante ya que necesitaré una envoltura adicional para cada tipo que tengo. ¿Hay alguna forma de evitar este patrón de envoltura o una forma más correcta / elegante de manejar esta situación?


Creé una extensión de ayuda para Codable que facilitará este tipo de cosas.

vea https://github.com/evermeer/Stuff#codable

Con eso puedes crear una instancia de tu objeto de usuario como este:

let v = User(json: json, keyPath: "user")

No tiene que cambiar nada en su estructura de usuario original y no necesita una envoltura.


La respuesta de Ollie es definitivamente la mejor manera de resolver este caso, pero sí introduce cierto conocimiento en la persona que llama, lo que puede ser indeseable. Tampoco es muy flexible. Todavía creo que es una gran respuesta y exactamente lo que quieres aquí, pero este es un buen ejemplo simple para explorar la codificación estructural personalizada.

¿Cómo podemos hacer que esto funcione correctamente?

let user = try? JSONDecoder().decode(User.self, from: json)

Ya no podemos usar las conformidades por defecto. Tenemos que construir nuestro propio decodificador. Eso es un poco tedioso, pero no difícil. Primero, necesitamos codificar la estructura en CodingKeys:

struct User { let id: Int let username: String enum CodingKeys: String, CodingKey { case user // The top level "user" key } // The keys inside of the "user" object enum UserKeys: String, CodingKey { case id case username } }

Con eso, podemos decodificar el User a mano extrayendo el contenedor anidado:

extension User: Decodable { init(from decoder: Decoder) throws { // Extract the top-level values ("user") let values = try decoder.container(keyedBy: CodingKeys.self) // Extract the user object as a nested container let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user) // Extract each property from the nested container id = try user.decode(Int.self, forKey: .id) username = try user.decode(String.self, forKey: .username) } }

Pero definitivamente lo haría a la manera de Ollie para este problema.

Para obtener más información sobre esto, consulte Codificación y decodificación de tipos personalizados .


Podría decodificar usando una combinación de diccionario: usuario y luego extraer el objeto de usuario. p.ej

struct User: Codable { let id: Int let username: String } let decoder = JSONDecoder() let userDictionary = try decoder.decode([String: User].self, from: jsonData)


Por supuesto, siempre puede implementar su propia decodificación / codificación personalizada, pero para este simple escenario su tipo de contenedor es una solución IMO mucho mejor;)

Para comparación, la decodificación personalizada se vería así:

struct User { var id: Int var username: String enum CodingKeys: String, CodingKey { case user } enum UserKeys: String, CodingKey { case id, username } } extension User: Decodable { init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user) self.id = try user.decode(Int.self, forKey: .id) self.username = try user.decode(String.self, forKey: .username) } }

y aún debe cumplir con el protocolo Encodable si también desea admitir la codificación. Como dije antes, su simple UserWrapper es mucho más fácil;)