DECODABLE en Swift en Español

DECODABLE en Swift en Español

Decodable en Swift se usa para parsear el JSON recibido de backend a objetos de tu dominio. Al recibir una petición HTTP con URLSession normalmente usamos JSONDecoder para mapear esta información a una struct de nuestra app. Con Decodable podemos mapear Arrays, usar estrategías, etc

SwiftBeta
Aprende Decodable en Swift

La librería Standard de Swift define una forma standard para codificar y decodicar datos. Para adoptar esta implementación debemos conformar en nuestros tipos los protocolos Encodable o Decodable.

Al adoptar estos protocolos podemos implementar el codificador y decodificador para codificar o decodificar a JSON o a property list (el formato de los property list es como el info.plist de una app).

Podemos conformar los dos a la vez usando Codable.

Es realmente útil para mapear de JSON a modelos de nuestro domino, una práctica que es muy común en la mayoría de aplicaciones que hacen peticiones HTTP.

Codificar y decodificar automáticamente

La manera más simple de hacer un tipo codable es usar propiedades que ya estén conformando el protocolo codable. Ejemplos claros son: String, Int, Double, Date, Data y URL.

Un ejemplo seria:

struct User: Codable {
    let name: String
    let age: Int
}

let data = """
{
    "name": "SwiftBeta",
    "age": 30
}
""".data(using: .utf8)!

let user = try? JSONDecoder().decode(User.self, from: data)
user?.name
Ejemplo simple de JSONDecoder en Swift

Hemos creado una struct llamada User que conforma el protocolo Codable. Como tiene propiedades de tipo String y tipo Int, no tenemos que hacer nada (ya que las dos conforman Codable).

Hemos creado la variable data para simular que es el JSON que recibimos al hacer una petición HTTP a nuestro backend.

Luego creamos una instancia de nuestro JSONDecoder y le decimos que intente decodificar lo que hay dentro de la variable data con nuestra User struct. Como ves, tenemos un try? ya que este mapeo puede fallar.

El ejemplo que acabamos de ver es muy sencillo, vamos a ver uno con más campos:

struct User: Decodable {
    let name: String
    let age: Int
}

struct Job: Decodable {
    let name: String
    let description: String
}

struct Curriculum: Decodable {
    var identifier: String
    var user: User
    var job: Job
}


let data = """
{
    "identifier": "123456789",
    "user": {
        "name": "SwiftBeta",
        "age": 30
    },
    "job": {
        "name": "Software Engineer",
        "description": "Knowledge Combine, SwiftUI, UIKit, RxSwift, Cocoapods, etc"
    }
}
""".data(using: .utf8)!

let curriculum = try? JSONDecoder().decode(Curriculum.self, from: data)
curriculum?.job.name
Ejemplo de JSONDecoder en Swift

Sin más, no hay ninguna complicación.

Coding Keys

Vamos a complicar aún más las cosas. La mayoría de veces cuando llamamos a un endpoint la key del JSON no coincide con el nombre de la propiedad de nuestro objeto de dominio. Fíjate que ahora la key donde viene el nombre del user es "name_of_user":

let data = """
{
    "name_of_user": "SwiftBeta",
    "age": 30
}
""".data(using: .utf8)!

struct User: Codable {
    let name: String
    let age: Int
    
    enum CodingKeys: String, CodingKey {
        case name = "name_of_user"
        case age
    }
}

let user = try? JSONDecoder().decode(User.self, from: data)
user?.name
Uso de CodingKeys con Decodable en Swift

Al implementar CodingKeys hacemos ese mapeo del JSON al nombre de la propiedad. Y el resultado es exactamente como el ejemplo anterior. Omitimos crear el enum CodingKeys cuando las keys del JSON coinciden con los de nuestra struct.

Decoder

Vamos a ver lo que ocurre en el inicializador con Decoder. Hasta ahora no lo hemos puesto ya que no lo hemos necesitado. En los ejemplos anteriores, Swift la ha generado por nosotros (cuando son casos sencillos, la mayoría de veces serán procesos transparentes para nosotros). Vamos a ver otra vez como quedaría el primer ejemplo, pero esta vez con todo el código que genera Swift por nosotros:

let data = """
{
    "name": "SwiftBeta",
    "age": 30
}
""".data(using: .utf8)!

struct User: Codable {
    let name: String
    let age: Int
    
    enum CodingKeys: String, CodingKey {
        case name
        case age
    }
}

extension User {
    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 user = try JSONDecoder().decode(User.self, from: data)
user.name
Init manual de JSONDecoder en Swift

Dentro del método init, le decimos al decodificador que tendremos un container con las CodingKeys que hemos especificado en el enum. Y luego, accedemos al container para sacar cada valor y asignarselo a la propiedades de nuestra instancia.

Decodificar un Array

Hasta ahora hemos visto un ejemplo sencillo de cómo mapear un una única instancia de nuestro modelo de dominio. Vamos a ver otro ejemplo para ver cómo mapeamos cuando recibimos un listado de usuarios.

let data = """
[{
    "name": "Swift",
    "age": 20
  },
  {
    "name": "Beta",
    "age": 30
  },
    {
    "name": "Blog",
    "age": 20
  },
  {
    "name": "iOS",
    "age": 30
  }
]
""".data(using: .utf8)!

struct User: Codable {
    let name: String
    let age: Int
}

let users = try JSONDecoder().decode([User].self, from: data)

users.forEach { user in
    print("User name: \(user.name)")
}
Cómo decodificar un Array con JSONDecoder

Como ves, es muy similar hasta lo ya visto en este post. La clave de todo está en esta línea, donde especificamos que esperamos un array de Users:

let users = try JSONDecoder().decode([User].self, from: data)

Estrategias para Decodificar

En Swift 4.1 añadieron la propierdad keyDecodingStrategy como propiedad del JSONDecoder para poder usar distintas estrategias. Por ejemplo hay empresas/equipos que prefieren usar snake case "user_name".
Si tienes algún endpoint que devuelve la key con este formato "user_name", que sepas que puedes transformarlo fácilmente usando keyDecodingStrategy:

let data = """
{
    "type_vehicle": "Car",
    "num_doors": 4
}
""".data(using: .utf8)!

struct Vehicle: Codable {
    let typeVehicle: String
    let numDoors: Int
}

let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

let vehicle = try jsonDecoder.decode(Vehicle.self, from: data)
vehicle.typeVehicle
Estrategias para decodificar con JSONDecoder en Swift

Hay más estrategías que puedes usar en tus decodificadores:

  • dateDecodingStrategy
  • dataDecodingStrategy
  • nonConformingFloatDecodingStrategy

Hoy hemos visto una pincelada de lo que son los decodificadores y como los usamos para mapear de JSON a nuestros modelos de negocio. Pero hay mucho más que iremos viendo.

Como siempre puedes encontrar el código en Github

Hasta aquí el post de hoy, gracias por leernos! 🤓
Si tienes preguntas no dudes en contactar con nosotros a través de Twitter

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


Network