Codable cheat sheet
Codable
fue una de las características fundamentales de Swift 4.0, trayendo consigo una conversión increíblemente suave entre tipos de datos Swift y JSON. Luego mejoró aún más en Swift 4.1 gracias a las nuevas funcionalidades que se añadieron, y espero que vengan cosas aún mayores en el futuro.
En este artículo quiero proporcionar ejemplos rápidos de código para ayudarte a resolver preguntas y problemas comunes, todo ello utilizando Codable
.
SPONSORED Construir y mantener la infraestructura de suscripción in-app es difícil. Por suerte hay una forma mejor. Con RevenueCat, puedes implementar suscripciones para tu aplicación en horas, no en meses, para que puedas volver a construir tu aplicación.
Pruébalo gratis
¡Patrocina Hacking with Swift y llega a la mayor comunidad Swift del mundo!
Codificación y decodificación de JSON
Empecemos por lo básico: convertir algo de JSON en structs Swift.
Primero, aquí tenemos algo de JSON con el que trabajar:
let json = """"""let data = Data(json.utf8)
La última línea lo convierte en un objeto Data
porque es con lo que trabajan los decodificadores Codable
.
A continuación, tenemos que definir una estructura Swift que contendrá nuestros datos terminados:
struct User: Codable { var name: String var age: Int}
Ahora podemos seguir adelante y realizar la decodificación:
let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}
Eso imprimirá «Paul», que es el nombre del primer usuario en el JSON.
Convirtiendo case
Un problema común con JSON es que utilizará un formato diferente para sus nombres de clave que el que queremos utilizar en Swift. Por ejemplo, puede que tengas «first_name» en tu JSON y necesites convertirlo en una propiedad firstName
.
Ahora, una solución obvia aquí es simplemente cambiar los tipos de JSON o de Swift para que usen la misma convención de nombres, pero no vamos a hacer eso aquí. En su lugar, voy a suponer que tienes un código como este:
let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}
Para que esto funcione sólo tenemos que cambiar una propiedad en nuestro decodificador JSON:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase
Eso le indica a Swift que mapee los nombres en mayúsculas y minúsculas (names_written_like_this) a nombres en mayúsculas y minúsculas (namesWrittenLikeThis).
Mapping different key names
Si tienes claves JSON que son completamente diferentes a tus propiedades Swift, puedes mapearlas usando un enum CodingKeys
.
Echa un vistazo a este JSON:
let json = """"""
Esos nombres de claves no son geniales, y realmente nos gustaría convertir esos datos en una estructura como esta:
struct User: Codable { var firstName: String var lastName: String var age: Int}
Para que eso ocurra necesitamos declarar un CodingKeys
enum: un mapeo que Codable
puede usar para convertir nombres JSON en propiedades para nuestra estructura. Se trata de un enum normal que utiliza cadenas para sus valores brutos, de modo que podemos especificar tanto el nombre de nuestra propiedad (el caso del enum) como el nombre JSON (el valor del enum) al mismo tiempo. También necesita ajustarse al protocolo CodingKey
, que es lo que hace que esto funcione con el protocolo Codable
.
Así que, añade este enum a la estructura:
enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}
Que ahora será capaz de decodificar el JSON como estaba previsto.
Nota: El enum se llama CodingKeys
y el protocolo se llama CodingKey
.
Trabajando con fechas ISO-8601
Hay muchas formas de trabajar con fechas en internet, pero la ISO-8601 es la más común. Codifica la fecha completa en formato AAAA-MM-DD, luego la letra «T» para señalar el inicio de la información horaria, luego la hora en formato HH:MM:SS, y finalmente una zona horaria. La zona horaria «Z», abreviatura de «Zulu time» se utiliza comúnmente para significar UTC.
Codable
es capaz de manejar ISO-8601 con un convertidor de fecha incorporado. Así, dado este JSON:
let json = """"""
Podemos decodificarlo así:
struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601
Eso permite el análisis sintáctico de la fecha ISO-8601, que convierte de 1999-04-03T17:30:31Z a una instancia Date
, a la vez que maneja la conversión de mayúsculas a minúsculas.
Trabajando con otras fechas comunes
Swift viene con soporte incorporado para otros tres formatos de fecha importantes. Se utilizan de la misma manera que las fechas ISO-8601, como se ha indicado anteriormente, por lo que sólo hablaré de ellas brevemente:
- El formato
.deferredToDate
es el formato de fecha propio de Apple, y registra el número de segundos y milisegundos desde el 1 de enero de 2001. Esto no es realmente útil fuera de las plataformas de Apple. - El formato
.millisecondsSince1970
registra el número de segundos y milisegundos desde el 1 de enero de 1970. Esto es bastante común en línea. - El formato
.secondsSince1970
registra el número de segundos enteros desde el 1 de enero de 1970. Esto es extremadamente común en línea, y es el segundo después de ISO-8601.
Trabajando con fechas personalizadas
Si su formato de fecha no coincide con una de las opciones incorporadas, no se desespere: Codable puede analizar las fechas personalizadas basándose en un formateador de fechas que usted cree.
Por ejemplo, este JSON rastrea el día en que un estudiante se graduó en la universidad:
let json = """"""
Eso utiliza el formato de fecha DD-MM-AAAA, que no es una de las opciones incorporadas de Swift. Afortunadamente, puedes proporcionar una instancia preconfigurada de DateFormatter
como estrategia de decodificación de fechas, así:
let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)
Trabajando con fechas raras
A veces obtendrás fechas tan extrañas que incluso DateFormatter
no puede manejarlas. Por ejemplo, podrías obtener JSON que almacena las fechas usando el número de días que han transcurrido desde el 1 de enero de 1970:
let json = """"""
Para hacer que eso funcione necesitamos escribir un decodificador personalizado para la fecha. Todo lo demás seguirá siendo manejado porCodable
– sólo estamos proporcionando un cierre personalizado que procesará la parte de las fechas.
Podría tratar de hacer algunas matemáticas hacky aquí como multiplicar el conteo de días por 86400 (el número de segundos en un día), a continuación, utilizando el método addTimeInterval()
de Date
. Sin embargo, eso no tendrá en cuenta el horario de verano y otros problemas de fechas, por lo que una mejor solución es utilizar DateComponents
y Calendar
así:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .custom { decoder in // pull out the number of days from Codable let container = try decoder.singleValueContainer() let numberOfDays = try container.decode(Int.self) // create a start date of Jan 1st 1970, then a DateComponents instance for our JSON days let startDate = Date(timeIntervalSince1970: 0) var components = DateComponents() components.day = numberOfDays // create a Calendar and use it to measure the difference between the two let calendar = Calendar(identifier: .gregorian) return calendar.date(byAdding: components, to: startDate) ?? Date()}
Atención: Si tiene que analizar muchas fechas, recuerde que este cierre se ejecutará para cada una de ellas – ¡hágalo rápido!
Analizar datos jerárquicos de forma fácil
Cualquier JSON no trivial es probable que tenga datos jerárquicos – una colección de datos anidados dentro de otra. ¡Por ejemplo:
let json = """"""
Codable
puede manejar esto muy bien, siempre y cuando usted puede describir las relaciones claramente.
Encuentro la forma más fácil de hacer esto es el uso de estructuras anidadas, así:
struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}
La desventaja es que si desea leer el nombre de un usuario, es necesario utilizar user.name.firstName
, pero al menos el trabajo de análisis es trivial – nuestro código existente ya funciona!
Paralizar datos jerárquicos de la manera más difícil
Si quieres parsear datos jerárquicos en una estructura plana – es decir, es decir, si quieres escribir user.firstName
en lugar de user.name.firstName
, tienes que hacer algún tipo de análisis. Esto no es demasiado difícil, sin embargo, y Codable
lo hace maravillosamente seguro.
En primer lugar, crear la estructura que desea terminar con:
struct User: Codable { var firstName: String var lastName: String var age: Int}
En segundo lugar, tenemos que definir las claves de codificación que describen donde los datos se pueden encontrar en la jerarquía.
Volvamos a mirar el JSON:
let json = """"""
Como puedes ver, en la raíz hay una clave llamada «nombre» y otra llamada «edad», así que tenemos que añadir eso como nuestras claves de codificación raíz. Pon esto dentro de tu estructura:
enum CodingKeys: String, CodingKey { case name, age}
Dentro de «nombre» había dos claves más, «nombre» y «apellido», así que vamos a crear algunas claves de codificación para esas dos. Añade esto:
enum NameCodingKeys: String, CodingKey { case firstName, lastName}
Ahora viene la parte difícil: tenemos que escribir un inicializador personalizado y un método de codificación personalizado para nuestro tipo. Empieza añadiendo este método vacío a tu estructura:
init(from decoder: Decoder) throws {}
Dentro de ella, lo primero que tenemos que hacer es intentar sacar un contenedor que podamos leer usando las claves de nuestro enum CodingKeys
, así:
let container = try decoder.container(keyedBy: CodingKeys.self)
Una vez hecho esto podemos intentar leer nuestra propiedad age
. Esto se hace de una forma segura: le dices el tipo que quieres decodificar (Int.self
para nuestra edad), junto con un nombre clave del enum CodingKeys
:
age = try container.decode(Int.self, forKey: .age)
A continuación tenemos que bajar un nivel para leer los datos de nuestro nombre. Como has visto antes, «nombre» es una clave de nivel superior dentro de nuestro enum CodingKeys
, pero en realidad es un contenedor anidado de otros valores que tenemos que leer dentro. Así que tenemos que sacar ese contenedor:
let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)
Y finalmente podemos leer dos cadenas para las claves .firstName
y .lastName
:
firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)
Eso termina el inicializador personalizado, pero aún nos queda un método por escribir: encode(to:)
. Esto es efectivamente el reverso del inicializador que acabamos de escribir, porque su trabajo es convertir nuestras propiedades de nuevo en contenedor anidado como es apropiado.
Esto significa crear un contenedor basado en nuestro enum CodingKeys
y escribir age
allí, luego crear un contenedor anidado basado en nuestro enum NameCodingKeys
, y escribir tanto firstName
como lastName
allí:
func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(age, forKey: .age) var name = container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) try name.encode(firstName, forKey: .firstName) try name.encode(lastName, forKey: .lastName)}
Eso completa todo nuestro código. Con eso en su lugar, ahora puede leer la propiedad firstName
directamente, que es mucho más agradable!
SPONSORED Construir y mantener la infraestructura de suscripción en la aplicación es difícil. Por suerte hay una forma mejor. Con RevenueCat, puedes implementar suscripciones para tu aplicación en horas, no en meses, para que puedas volver a construir tu aplicación.
Pruébalo gratis
¡Patrocina Hacking with Swift y llega a la mayor comunidad Swift del mundo!
Leave a Reply