JSONDecoder genérico

No momento, a grande maioria dos aplicativos móveis é cliente-servidor. Em todos os lugares há carregamento, sincronização, envio de eventos e a principal maneira de interagir com o servidor é trocar dados usando o formato json.


Decodificação de chave


A Fundação possui dois mecanismos para serializar dados mortos. Antigo é NSJsonSerialization e novo é Codable . O último da lista de vantagens tem uma coisa tão maravilhosa quanto a geração automática de chaves para dados json com base em uma estrutura (ou classe) que implementa Codable ( Encodable , Decodable ) e um inicializador para decodificar dados.


E tudo parece estar bem, você pode usar e aproveitar, mas a realidade não é tão simples.
Muitas vezes, no servidor, você pode ver o json do formulário:


 {"topLevelObject": { "underlyingObject": 1 }, "Error": { "ErrorCode": 400, "ErrorDescription": "SomeDescription" } } 

Este é um exemplo quase real de um dos servidores de projeto.


Para a classe JsonDecoder , JsonDecoder pode especificar o trabalho com as teclas snake_case, mas e se tivermos UpperCamelCase, dash-snake-case ou até uma mistura geral, mas não quisermos escrever as chaves manualmente?


Felizmente, a Apple forneceu a capacidade de configurar o mapeamento de chaves antes de CodingKeys lo com a estrutura JSONDecoder.KeyDecodingStrategy usando JSONDecoder.KeyDecodingStrategy . Vamos tirar proveito disso.


Primeiro, crie uma estrutura que implemente o protocolo CodingKey , porque não há nenhuma na biblioteca padrão:


  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 } } 

Então é necessário processar separadamente cada caso de nossas chaves. Os principais:
snake_case, case-snake-case, lowerCamelCase e UpperCamelCase. Verifique, corra, tudo funciona.


Então, encontramos um problema bastante esperado: abreviações em camelCase'ah (lembre-se das numerosas id , Id , ID ). Para fazê-lo funcionar, você precisa convertê-los corretamente e introduzir uma regra - as abreviações são convertidas em camelCase, mantendo apenas a primeira letra maiúscula e o myABBRKey se transformará em myAbbrKey .


Esta solução funciona muito bem para combinações de vários casos.


Nota: A implementação será fornecida em. Estratégia de decodificação de chave .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) } } 

Decodificação de data


O próximo problema de rotina é a maneira como as datas são passadas. Existem muitos microsserviços no servidor, há um pouco menos de equipes, mas também uma quantidade decente e, no final, somos confrontados com vários formatos de data como "Sim, eu uso o padrão". Além disso, alguém passa datas em uma sequência, alguém no período da época. Como resultado, novamente temos uma mistura de combinações de string-número-fuso horário-milissegundo-separador, e o DateDecoder no iOS reclama e exige um formato de data estrito. A solução aqui é simples: basta procurarmos sinais de um formato específico e combiná-los, obtendo o resultado necessário. Esses formatos cobriram com êxito e completamente meus casos.


Nota: Esse é o inicializador personalizado do DateFormatter. É só definir o formato para o formatador criado.


 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") 

JSONDecoder.DateDecodingStrategy isso ao nosso decodificador usando JSONDecoder.DateDecodingStrategy e obtemos um decodificador que processa quase tudo e o converte em um formato que pode ser digerido por nós.


Testes de desempenho


Os testes foram realizados para seqüências json de tamanho 7944 bytes.


estratégia convertFromSnakeCaseestratégia anyCodingKey
Absoluto0,001700,00210
Parente81%100%

Como podemos ver, o Decoder personalizado Decoder 20% mais lento devido à verificação obrigatória de cada chave no json para a necessidade de transformação. No entanto, essa é uma pequena taxa por não ter que registrar explicitamente chaves para estruturas de dados implementando Codable . O número de placas da caldeira é bastante reduzido no projeto com a adição deste decodificador. Devo usá-lo para economizar tempo do desenvolvedor, mas piorar o desempenho? Depende de você.


Código de amostra completo na biblioteca do github


Artigo em inglês

Source: https://habr.com/ru/post/pt449870/


All Articles