Por el momento, la gran mayoría de las aplicaciones móviles son cliente-servidor. En todas partes hay carga, sincronización, envío de eventos y la forma principal de interactuar con el servidor es intercambiar datos utilizando el formato json.
Decodificación clave
La Fundación tiene dos mecanismos para serializar datos muertos. Lo antiguo es NSJsonSerialization
y lo nuevo es Codable
. El último en la lista de ventajas tiene algo tan maravilloso como la generación automática de claves para datos json basada en una estructura (o clase) que implementa Codable
( Encodable
, Encodable
) y un inicializador para decodificar datos.
Y todo parece estar bien, puedes usarlo y disfrutarlo, pero la realidad no es tan simple.
Muy a menudo en el servidor puede ver json del formulario:
{"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } }
Este es un ejemplo casi real de uno de los servidores del proyecto.
Para la clase JsonDecoder
, puede especificar el trabajo con las teclas snake_case, pero ¿qué sucede si tenemos UpperCamelCase, dash-snake-case o incluso un batiburrillo general, pero no queremos escribir las claves manualmente?
Afortunadamente, Apple brindó la capacidad de configurar la asignación de teclas antes de hacerla coincidir con la estructura JSONDecoder.KeyDecodingStrategy
usando JSONDecoder.KeyDecodingStrategy
. Aprovecharemos esto.
Primero, cree una estructura que implemente el protocolo CodingKey
, porque no hay ninguno en la biblioteca estándar:
struct AnyCodingKey: CodingKey { var stringValue: String var intValue: Int? init(_ base: CodingKey) { self.init(stringValue: base.stringValue, intValue: base.intValue) } init(stringValue: String) { self.stringValue = stringValue } init(intValue: Int) { self.stringValue = "\(intValue)" self.intValue = intValue } init(stringValue: String, intValue: Int?) { self.stringValue = stringValue self.intValue = intValue } }
Entonces es necesario procesar por separado cada caso de nuestras claves. Los principales:
snake_case, dash-snake-case, lowerCamelCase y UpperCamelCase. Comprueba, corre, todo funciona.
Luego nos encontramos con un problema bastante esperado: abreviaturas en camelCase'ah (recuerde los numerosos id
, Id
, ID
). Para que funcione, debe convertirlos correctamente e introducir una regla: las abreviaturas se convierten a camelCase, manteniendo solo la primera letra mayúscula y myABBRKey se convertirá en myAbbrKey .
Esta solución funciona muy bien para combinaciones de varios casos.
Nota: La implementación se proporcionará en. Estrategia de decodificación de clave .custom
.
static func convertToProperLowerCamelCase(keys: [CodingKey]) -> CodingKey { guard let last = keys.last else { assertionFailure() return AnyCodingKey(stringValue: "") } if let fromUpper = convertFromUpperCamelCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromUpper) } else if let fromSnake = convertFromSnakeCase(initial: last.stringValue) { return AnyCodingKey(stringValue: fromSnake) } else { return AnyCodingKey(last) } }
Decodificación de fecha
El siguiente problema de rutina es la forma en que se pasan las fechas. Hay muchos microservicios en el servidor, hay un poco menos de equipos, pero también una cantidad decente, y al final nos enfrentamos con un montón de formatos de fecha como "Sí, uso el estándar". Además, alguien pasa las fechas en una cadena, alguien en la época de la época. Como resultado, nuevamente tenemos una mezcla de combinaciones de cadena-número-zona horaria-milisegundo-separador, y DateDecoder
en iOS se queja y requiere un formato de fecha estricto. La solución aquí es simple, solo buscando buscamos signos de un formato particular y los combinamos, obteniendo el resultado necesario. Estos formatos han cubierto con éxito y completamente mis casos.
Nota: Este es un inicializador personalizado de DateFormatter. Simplemente establece el formato para formateador creado.
static let onlyDate = DateFormatter(format: "yyyy-MM-dd") static let full = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSSx") static let noWMS = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ssZ") static let noWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss.SSS") static let noWMSnoWTZ = DateFormatter(format: "yyyy-MM-dd'T'HH:mm:ss")
Adjuntamos esto a nuestro decodificador usando JSONDecoder.DateDecodingStrategy
y obtenemos un decodificador que procesa casi cualquier cosa y lo convierte a un formato que sea digerible para nosotros.
Pruebas de rendimiento
Se realizaron pruebas para cadenas json de tamaño 7944 bytes.
Como podemos ver, el Decoder
personalizado Decoder
20% más lento debido a la verificación obligatoria de cada clave en json por la necesidad de transformación. Sin embargo, esta es una pequeña tarifa por no tener que registrar explícitamente claves para estructuras de datos mediante la implementación de Codable
. El número de placa de caldera se reduce mucho en el proyecto con la adición de este decodificador. ¿Debo usarlo para ahorrar tiempo al desarrollador, pero empeorar el rendimiento? Depende de usted.
Código de muestra completo en la biblioteca github
Artículo en inglés