JSONDecoder y decodeIfPresent en SWIFT en Español

JSONDecoder y decodeIfPresent en SWIFT en Español

decodeIfPresent en Swift lo usamos para mapear el JSON recibido y transformarlo a nuestros modelos. Al usar JSONDecoder en Swift puede que tengamos keys en el JSON que no se estén enviando, esto puede hacer crashear nuestra app. Para evitarlo usamos decodeIfPresent dentro del init de JSONDecoder.

SwiftBeta
JSONDecoder y decodeIfPresent en Swift

Cuando obtenemos el JSON de nuestro endpoint tenemos que ir con cuidado al mapear a nuestro modelo de dominio. Puede parecer algo trivial, pero aveces tenemos bugs/crashes en producción y nos cuesta entender el motivo.

Aveces la documentación de nuestro endpoint no está actualizada, o el contrato que teníamos con backend ha cambiado. Si tenemos un modelo de dominio sencillo como el siguiente entendemos que la propiedad age es opcional:

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

Pero, ¿esto qué significa? ¿tengo que recibir la key en el JSON?:

{
    "age": null,
    "name": "SwiftBeta"
}

¿o puedo directamente recibir?:

{
    "name": "SwiftBeta"
}
Depende el contrato que tengamos con backend puedes hacerlo de una manera u otra. Es decir, si el valor es null, quizás no quieres recibir ese valor en el JSON.

Si nuestro modelo de dominio es simple y no necesitamos crear el init con el Decoder (recuerda que este lo genera Swift por nosotros), entonces todo está bien. Es decir, esto funcionaría para ambos casos (recibiendo age con el valor null, o no recibiendo esta key en el JSON):

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

let user = try JSONDecoder().decode(User.self, from: data)
user.age

Hasta aquí todo bien, pero si tenemos que crear nuestro init con el Decoder podemos cometer errores, vamos a ver por qué.

Si tenemos el siguiente código:

let data = """
{
    "name": "Loop Infinito"
}
""".data(using: .utf8)!

struct User: Codable {
    let name: String
    let age: Int?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int?.self, forKey: .age)
    }
}

let user = try JSONDecoder().decode(User.self, from: data)
user.age
Uso de decode en Swift

Nos aparece el siguiente error:

Playground execution terminated: An error was thrown and was not caught:
▿ DecodingError
  ▿ keyNotFound : 2 elements
    - .0 : CodingKeys(stringValue: "age", intValue: nil)
    ▿ .1 : Context
      - codingPath : 0 elements
      - debugDescription : "No value associated with key CodingKeys(stringValue: \"age\", intValue: nil) (\"age\")."
      - underlyingError : nil

El error significa que ha intentado parsear la key age, pero no ha podido ya que no aparecía en nuestro JSON. Si sabes que hay keys que puede que no vengan en nuestro JSON, éstas a parte de ser opcionales, también debemos usar el decodeIfPresent.

Con esto nos aseguramos que si la key viene o no, no nos dará un crash en nuestra aplicación.

Vamos a ver el anterior ejemplo, pero asegurándonos que la app no tenga un crash:

let data = """
{
    "name": "Loop Infinito"
}
""".data(using: .utf8)!

struct User: Codable {
    let name: String
    let age: Int?
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        age = try container.decodeIfPresent(Int.self, forKey: .age)
    }
}

let user = try JSONDecoder().decode(User.self, from: data)
user.age
Uso de decodeIfPresent en Swift

Fíjate en la línea

age = try container.decodeIfPresent(Int.self, forKey: .age)

Con este sutil cambio, hemos añadido seguridad a un posible cambio en el endpoint. Con esto, si no recibimos un key asignaría nil a la propiedad age. Que es exactamente lo que queremos.

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