Saltar al contenido →

Guía de Codable con Swift

Desde el año pasado los desarrolladores de Swift/Objective-C disponemos de otra herramienta que facilita el serializado/deserializado de una manera rápida y sencilla.

Gracias al protocolo Codable nunca ha sido tan sencillo mapear, por ejemplo, los resultados de las API RESTful de datos con las entidades de nuestas Apps.

En esta guía veremos cómo hacer todo esto y algún que otro truco más.

Para ilustrar el artículo vamos a consumir un archivo JSON que contiene información de todas las versiónes de OSX y macOS obtenida de la Wikipedia.

Desmitificando Codable

Lo primero es decirte que Codable es en realidad un alias.

Y lo segundo es que Codable no necesariamente quiere decir JSON. Tanto Encodable como Decodable se diseñaron para que los desarrolladores pudiéramos convertir nuestros tipos a una representación externa.

Esto quiere decir que, además de exportar/importar a JSON, que de eso se encargan JSONEncoder JSONDecoder, podríamos desarrollar clases o estructuras que implementando Codable pudieran convertir los tipos de datos Swift en, por ejemplo, formato CSV u otro definido por nosotros.

Datos predefinidos

Los chicos del Apple Park nos echan una mano en esto y han implementando el protocolo Codable en varios de los tipos de datos más usados en nuestro día a día.

Comunes Contenedores Para UI ¿Estos también?
Bool Array CGPoint Locale
Int Dictionary CGRect Measurement
Float Set CGSize DateComponents
Double Optional CGAffineTransform Calendar
Decimal CGVector IndexPath
Data UIEdgeInsets MPMusicPlayerPlayParameters
Date UIOffset
String
URL

Todos estos tipos, y alguno más, ya pueden ser serializados/deserializados sin que tengamos que hacer nada por nuestra parte.

Tipos de datos propios

Eso está muy bien, pero claro, en nuestras apps usamos entidades de datos más complejas (léase estructuras, clases, enumeraciones…) y necesitamos serializarlas en formato JSON también

Que no cuando el pánico porque dos sencillos pasos podemos hacer que nuestro tipo de datos se serialize en JSON y al revés

Mapeando el documento JSON con nuestra estructura

Lo primero que tenemos que saber es que nuestra estrucura es una representación del documento JSON al que queremos asociarlos.

Así que todas las entidades que queramos serializar o deserialzar deben indicar que implementan en protocol Codable

Dicho de otra forma, cada propiedad de nuestra estructura/clase debe coincidir con una valor del documento JSON

El protocolo Codable hace esto comparando el nombre de nuestras propiedades con el nombre de las claves del documento JSON.

¿Quiere esto decir que deben llamarse exactamente igual? ¿O que no podemos tener propiedades que no estén presentes en el documento JSON? No, podemos tanto llamar a nuestras propiedades de manera diferente como tener propiedades que no estén presentes en el archivo JSON.

Para poder hacerlo tenemos que incluir una enumeración a la que llamaremos CodingKeys que implementa el protocolo CodingKey y que contendrá la lista de variables que serán empleadas por Codable.

Si el nombre de alguna variable no coincide con su homólogo en el documento JSON debemos establecer como tipo String el raw value de la enumeración y asignando un valor al caso de la enumeración que se usará durante la serialización y/o deserialización

En la imagen inferior podéis ver como se produce esta casación de variables.

Como véis a la propiedad platform no la he asignado un valor asociado ya que el nombre coincide con el de la propiedad del documento JSON a la que está asociada.

¿Y si mi tipo tiene como propiedades otro de mis tipos?

No tendrás ningún problema mientras el tipo que tenemos como variable cumpla con el protocolo Codable

En el ejemplo justamente eso, el tipo Version contiene propiedades un par de Release, un array de Platform y otro de Languages

Al cumplir éstas con el protocolo Codable no hay problema a la hora de serializar/deserializar el tipo Version.

Trabajando con formatos de fecha

Ya hemos visto que uno de los tipos predefinidos por el sistema es Date, pero las fechas tiene un problema subyacente, y no es otro que su formato.

No hablamos ya de su representación dependiendo de cada páis, sino que podemos expresarla omitiendo la parte horaria, como el número de segundos desde 1970, o de milisegundos…

Para poner orden en este desaguisado las clases JSONEncoder y JSONDecoder nos dejan establecer la estrategía con respecto al formato de fechas. Esto se hace mediante la propiedad dateEncodingStrategy o dateDecodingStrategy dependiendo de si es para JSONEncoder o JSONDecoder.

Em ambos casos es una enumeración que nos ofrece una serie de formatos predefinidos como pueden ser…

  • deferredToDate. Es el caso por defecto

  • iso8601. Podéis leer esta entrada de la Wikipedia donde está todo la información sobre este formato

  • millisecondsSince1970. Sería algo como esto 1545079182315. Es el que emplea twitter.

  • secondsSince1970. En este caso en lugar de milisegundos con los segundos.

Si ninguno de estos formatos se ajusta a lo que esperamos en el documento JSON podemos definir el nuestro propio. Basta con crear un DateFormatter con el formato de fecha que vamos a emplear y asígnarlo como valor asociado al caso de la enumeración

  • formatted(DateFormatter)

En nuestro ejemplo nos vemos obligados a definir nuestro propio formato de fecha, así que creamos nuestro DateFormatter…

y luego lo usamos cuando vayamos a leer un documento JSON…

…o cuando queramos convertir nuestro estrutura en una representación JSON.

Codable para Enum

Aquí estamos entrando en terreno farragoso, por lo que vamos a ir del caso más sencillo al más complicado.

El más sencillo es el que se da para las enumeración Platform y Language es decir, cuando un valor del archivo JSON se corresponde con un caso de la enumeración.

¿Y qué pasa cuando la enumeración tiene valores asociados?

Y aquí es donde la cosa se complica.

Lo primero que te voy a decir es que vamos a usar una estructura privada en la que nos apoyaremos a la hora de ejecutar las operaciones de serialización/deserialización.

Vamos a echar un vistazo a la enumeración Release. Como podéis ver sus dos casos tienen valores asociados

  • initial(os, version)
  • latest(os, version, update)

Y en el archivo JSON se define de la siguiente manera.

Efectivamente la definición es un poco rara, pero es así para poder ilustrar este caso en concreto.

Y lo que te estás preguntando es… Cómo voy de ese JSON a esta enumeración de Swift. Vamos a apoyarnos en la imagen de abajo para explicar la secuencia de los hechos.

  • A JSONDecoder le decimos que esperamos un Array del tipo Version.
  • Cuando llega una Version se hace la casación de propiedades Swift con sus homólogas en JSON

  • Al llegar a Release ve que es un tipo definido por nosotros y que implementa Codable. Le cede el testigo a Release para que siga deserializando

  • Release ha especificado en su enumeración CodingKeys que puede tener dos valores, initial o latest

  • JSONDecoder se percata de que Release ha sobreescrito el inicializador init(from:) por lo que le pasa el control a él para que siga con el proceso.

  • El constructor obtiene el contenedor, que es un KeyedDecodingContainer, y vamos preguntando si lo que viene es la propiedad initial o latest gracias a la función decodeIfPresent( :forKey:). A esta función le pasamos como parámetros el tipo de dato que representa la clave. En nuestro caso son las estructuras de ayuda en las que nos apoyamos para serializar y deserializar.

  • En el caso de serialización se sobreescribe la función func encode(to encoder: Encoder) throws.

Y ahora veamos es código responsable de todo eso que acabamos de decir.

Codable para imágenes

Una de las cosas que más se preguntan a Google o Bing es si se puede enviar una imagen mediante JSON.

La respuesta es que sí, pero el rendimiento dicta que no se debe hacer, así que, lo que viene a continuación debe emplearse en caso muy extremos, o no emplearse.

Una imagen no deja ser una sucesió de bytes, y en el framework Foundation tenemos la estructura Data que se encarga de representar cadenas de bytes.

Si nos fijamos de nuevo en la lista de tipos que por defecto conforman el protocolo Codable, o lo que es lo mismo, Encodable y Decodable, vemos que Data está entre ellos, así que bastaría con incluir nuestra variables Data en la lista de atributos que se pueden serializar/deserializar y listo.

El resultado lo podéis ver en el archivo JSON que sirve de base al artículo y que podéis encontrar en este repositorio de GitHub.

El archivo tiene un tamañó de más de 3 megas, lo que le convirte en poco menos que inútil para consultarlo online.

En lugar de incluir las imágenes en formato Data lo normal es indicar la URL en la que está alojada la imagen (normalmente un CDN). De esta manera el JSON rebaja su tamaño de forma considerable.

Y ya por fin…

Ha llegado la hora de deserializar o de serializar los documentos JSON, se hace de esta manera…

Código fuente

En este repositorio público de GitHub encontraréis el Playground junto con el archivo JSON.

Enlaces de interés

Publicado en Foundation Swift