JSONDecoder y nestedContainer en SWIFT
nestedContainer en Swift nos sirve para acceder a JSON anidado de una respuesta HTTP. Es muy útil para coger las keys del JSON que queremos y añadirlas a nuestro modelo sin crear modelos que no necesitamos. Aprende a usar nestedContainer y JSONDecoder en Swift.
Tabla de contenido
Hoy en SwiftBeta vamos a ver el nestedContainer. Cuando realizamos una petición HTTP a nuestro backend podemos recibir información excesiva en el JSON, con muchos datos anidados, es decir, quizás de todas las keys que nos llegan solo queremos unas pocas para crear nuestro modelo.
Hasta ahora habíamos visto a cómo parsear este JSON a un modelo de nuestra app. Es decir para este JSON:
let data = """
{
"name_of_user": "SwiftBeta",
"age": 30,
"address": {
"city": "Barcelona",
"zip": "12345",
"street": "Paseo SwiftBeta, Número 1"
}
}
""".data(using: .utf8)!
podríamos crear dos structs, una llamada User y la otra llamada Address:
struct Address: Decodable {
let city: String
let zip: String
}
struct User: Decodable {
let name: String
let age: Int
let address: Address
enum CodingKeys: String, CodingKey {
case name = "name_of_user"
case age
case address
}
}
Y mapearíamos el JSON a nuestro modelo de la siguiente manera:
let user = try JSONDecoder().decode(User.self, from: data)
user.address.city // Barcelona
En muchos casos esto nos serviría para nuestra app, pero hoy os voy a mostrar cómo extraer el valor "city" de la key "address" sin crear un nuevo modelo (es decir, no nos haría falta tener el modelo Address, podríamos tener una property llamada city en User directamente). Esto nos es muy útil para no ir generando modelos en valores nesteados de nuestro JSON.
¿Cómo lo vamos hacer? Lo primero que vamos hacer es crear nuestro init(from decoder: Decoder) throws como ya hemos visto en otros videos.
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.age = try container.decode(Int.self, forKey: .age)
self.address = try container.decode(Address.self, forKey: .address)
}
Hasta aquí todo perfecto, si compilamos sigue funcionando todo perfectamente. Ahora en lugar de usar esta línea para mapear el JSON con la key address al modelo Address lo que vamos hacer es usar la propiedad nestedContainer:
self.address = try container.decode(Address.self, forKey: .address)
Lo siguiente que vamos hacer es:
- Cambiar la property Address en la struct User. La vamos a renombrar a city y va a ser de tipo String.
- En el enum de CodingKeys de la struct User vamos a añadir un nuevo caso llamado city.
Como resultado tendríamos:
Si intentamos compilar nos fallará. Debemos ir a la línea donde tenemos self.address y hacer unos cambios.
Lo que vamos a añadir son estas dos nuevas líneas:
let address = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .address)
self.city = try address.decode(String.self, forKey: .city)
Al usar el nestedContainer en container con la key address lo que hacemos es acceder a la información del JSON que tiene esa key (es decir, la parte de city, zip y street). Y se lo asignamos a una constante que hemos llamado address. Ahora, lo que debemos hacer es acceder a este container address y decodificar el valor del JSON que tenga la key city, y se lo asignamos a la propiedad city de la struct User.
De esta manera, el código quedaría así:
Y si compilamos vemos que el contenido de la propiedad city en User es Barcelona
let user = try JSONDecoder().decode(User.self, from: data)
user.city