JSONDecoder y nestedContainer en SWIFT

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.

SwiftBeta
JSONDecoder y nestedContainer en Swift

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:

struct User: Decodable {
    let name: String
    let age: Int
    let city: String
    
    enum CodingKeys: String, CodingKey {
        case name = "name_of_user"
        case age
        case address
        case city
    }
    
    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)
    }
}
Decode un JSON con modelos en Swift

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í:

struct User: Decodable {
    let name: String
    let age: Int
    let city: String
    
    enum CodingKeys: String, CodingKey {
        case name = "name_of_user"
        case age
        case address
        case city
    }
    
    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)

let address = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .address)
        self.city = try address.decode(String.self, forKey: .city)
    }
}
Decode un JSON con nestedContainer

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

Si quieres seguir aprendiendo sobre SwiftUI, Swift, Xcode, o cualquier tema relacionado con el ecosistema Apple


Network