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