Folha de fraude codificável

Codable foi uma das características fundamentais da Swift 4.0, trazendo consigo uma conversão incrivelmente suave entre os tipos de dados Swift e JSON. Depois ficou ainda melhor no Swift 4.1 graças à nova funcionalidade sendo adicionada, e espero que coisas ainda maiores venham no futuro.

Neste artigo eu quero fornecer amostras rápidas de código para ajudá-lo a resolver questões comuns e resolver problemas comuns, tudo usando Codable.

Hacking with Swift é patrocinado pela RevenueCat

SPONSORED Construir e manter a infra-estrutura de subscrição no sistema é difícil. Felizmente, há uma maneira melhor. Com RevenueCat, você pode implementar assinaturas para seu aplicativo em horas, não em meses, para que você possa voltar a construir seu aplicativo.

Tente-o de graça

Sponsor Hacking com a Swift e alcançar a maior comunidade Swift do mundo!

Codificação e descodificação de JSON

Comecemos com o básico: convertendo alguns JSON em estruturas Swift.

Primeiro, aqui está algum JSON para trabalhar:

let json = """"""let data = Data(json.utf8)

A última linha converte-o para um objecto Data porque é com isso que CodableOs descodificadores funcionam.

Próximo precisamos definir uma estrutura Swift que irá manter nossos dados acabados:

struct User: Codable { var name: String var age: Int}

Agora podemos avançar e executar a decodificação:

let decoder = JSONDecoder()do { let decoded = try decoder.decode(.self, from: data) print(decoded.name)} catch { print("Failed to decode JSON")}

Que irá imprimir “Paul”, que é o nome do primeiro usuário no JSON.

Convertendo caso

Um problema comum com JSON é que ele irá usar formatação diferente para seus nomes chaves do que nós queremos usar no Swift. Por exemplo, você pode obter “first_name” no seu JSON e precisar converter isso para um firstName property.

Agora, uma solução óbvia aqui é apenas mudar ou o JSON ou os seus tipos Swift para que eles usem a mesma convenção de nomes, mas nós não vamos fazer isso aqui. Em vez disso, vou assumir que você tem um código como este:

let json = """"""let data = Data(json.utf8)struct User: Codable { var firstName: String var lastName: String}

Para fazer isso funcionar, precisamos alterar apenas uma propriedade no nosso decodificador JSON:

let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCase

Que instrui o Swift a mapear nomes de caixas de serpentes (names_written_like_this) para nomes de caixas de camelos (namesWrittenLikeThis).

Maptando diferentes nomes de chaves

Se você tem chaves JSON que são completamente diferentes das suas propriedades Swift, você pode mapeá-las usando um enumero CodingKeys.

Dê uma olhada neste JSON:

let json = """"""

Os nomes das chaves não são grandes, e realmente gostaríamos de converter esses dados em uma estrutura como esta:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Para que isso aconteça precisamos declarar um CodingKeys enum: um mapeamento que Codable pode usar para converter nomes JSON em propriedades para a nossa estrutura. Este é um enumero regular que usa strings para seus valores brutos para que possamos especificar tanto o nome de nossa propriedade (o enum caso) quanto o nome JSON (o valor do enum) ao mesmo tempo. Ele também precisa estar de acordo com o protocolo CodingKey, que é o que faz este trabalho com o protocolo Codable.

Então, adicione este enum à estrutura:

enum CodingKeys: String, CodingKey { case firstName = "user_first_name" case lastName = "user_last_name" case age}

Que agora poderá decodificar o JSON como planejado.

Nota: O enumero é chamado CodingKeys e o protocolo é chamado CodingKey.

Trabalhar com datas ISO-8601

Existem muitas formas de trabalhar com datas na Internet, mas a ISO-8601 é a mais comum. Ele codifica a data completa no formato AAAA-MM-DD, depois a letra “T” para sinalizar o início da informação da hora, depois a hora no formato HH:MM:SS, e finalmente um fuso horário. O fuso horário “Z”, abreviatura para “Zulu time” é comumente usado para significar UTC.

Codable é capaz de lidar com ISO-8601 com um conversor de data incorporado. Assim, dado isto JSON:

let json = """"""

Podemos descodificá-lo assim:

struct Baby: Codable { var firstName: String var timeOfBirth: Date}let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .iso8601

Ativa a análise de datas ISO-8601, que converte de 1999-04-03T17:30:31Z para uma instância Date, ao mesmo tempo que manuseia a caixa de serpente para conversão de caixa de camelo.

Trabalhar com outras datas comuns

Swift vem com suporte incorporado para três outros formatos de datas importantes. Você usa-os tal como usa datas ISO-8601 como mostrado acima, por isso vou apenas falar brevemente sobre eles:

  • O formato .deferredToDate é o próprio formato de datas da Apple, e ele acompanha o número de segundos e milissegundos desde 1 de Janeiro de 2001. Isto não é realmente útil fora das plataformas da Apple.
  • O formato .millisecondsSince1970 acompanha o número de segundos e milissegundos desde 1 de Janeiro de 1970. Isto é bastante comum online.
  • O formato .secondsSince1970 acompanha o número de segundos inteiros desde 1 de Janeiro de 1970. Isto é extremamente comum online, e é apenas o segundo lugar na ISO-8601.

Trabalhar com datas personalizadas

Se o seu formato de data não corresponde a uma das opções incorporadas, não desespere: Codable pode analisar datas personalizadas com base em um formatador de data que você cria.

Por exemplo, este JSON acompanha o dia em que um estudante se formou na universidade:

let json = """"""

Que usa o formato de data DD-MM-YYYY, que não é uma das opções embutidas da Swift. Felizmente, você pode fornecer uma instância pré-configurada DateFormatter como estratégia de decodificação de datas, como esta:

let formatter = DateFormatter()formatter.dateFormat = "dd-MM-yyyy"let decoder = JSONDecoder()decoder.keyDecodingStrategy = .convertFromSnakeCasedecoder.dateDecodingStrategy = .formatted(formatter)

Trabalhar com datas estranhas

Algumas vezes você terá datas tão estranhas que mesmo DateFormatter não consegue lidar com elas. Por exemplo, você pode obter JSON que armazena datas usando o número de dias decorridos desde 1 de janeiro de 1970:

let json = """"""

Para fazer isso funcionar precisamos escrever um decodificador personalizado para a data. Tudo o resto ainda será tratado por Codable – estamos apenas fornecendo um encerramento personalizado que irá processar a parte das datas.

Você poderia tentar fazer alguma matemática hacky aqui, como multiplicar a contagem de dias por 86400 (o número de segundos em um dia), então usando o método addTimeInterval() de Date. No entanto, isso não levará em conta o horário de verão e outros problemas de data, então uma solução melhor é usar DateComponents e Calendar assim:

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

Aviso: Se tiver de analisar muitas datas, lembre-se que este encerramento será executado para cada uma delas – torne-o rápido!

Partilhar dados hierárquicos da forma mais fácil

Todos os JSON não triviais terão provavelmente dados hierárquicos – uma recolha de dados aninhada dentro de outra. Por exemplo:

let json = """"""

Codable é capaz de lidar com isto muito bem, desde que possa descrever as relações claramente.

A forma mais fácil de o fazer é usando estruturas aninhadas, como esta:

struct User: Codable { struct Name: Codable { var firstName: String var lastName: String } var name: Name var age: Int}

A desvantagem é que se quiser ler o primeiro nome de um utilizador, precisa de usar user.name.firstName, mas pelo menos o trabalho de análise real é trivial – o nosso código existente já funciona!

Partilhar dados hierárquicos da forma mais difícil

Se quiser analisar os dados hierárquicos numa estrutura plana – isto é.., você quer ser capaz de escrever user.firstName em vez de user.name.firstName – então você mesmo precisa fazer uma análise. Isto não é muito difícil, no entanto, e Codable faz com que escreva de forma bonita e segura.

Primeiro, crie a estrutura com que quer acabar:

struct User: Codable { var firstName: String var lastName: String var age: Int}

Segundo, precisamos de definir chaves de codificação que descrevam onde os dados podem ser encontrados na hierarquia.

Vejamos novamente o JSON:

let json = """"""

Como você pode ver, na raiz há uma chave chamada “nome” e outra chamada “idade”, então precisamos adicionar isso como nossas chaves codificadoras raiz. Coloque isto dentro da sua estrutura:

enum CodingKeys: String, CodingKey { case name, age}

No interior de “nome” estavam mais duas chaves, “primeiro_nome” e “último_nome”, por isso vamos criar algumas chaves de codificação para essas duas. Adicione isto:

enum NameCodingKeys: String, CodingKey { case firstName, lastName}

Agora para a parte difícil: precisamos escrever um inicializador personalizado e um método de codificação personalizado para o nosso tipo. Comece adicionando este método vazio à sua estrutura:

init(from decoder: Decoder) throws {}

Aí dentro, a primeira coisa que precisamos fazer é tentar puxar um container que possamos ler usando as chaves do nosso CodingKeys enum, assim:

let container = try decoder.container(keyedBy: CodingKeys.self)

A partir daí, podemos tentar ler a nossa propriedade age. Isto é feito de uma forma segura: você diz o tipo que você quer decodificar (Int.self para a nossa idade), junto com um nome de chave do CodingKeys enum:

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

Próximo, precisamos cavar um nível para ler os dados do nosso nome. Como você viu anteriormente, “nome” é uma chave de nível superior dentro do nosso CodingKeys enum, mas na verdade é um recipiente aninhado de outros valores que precisamos ler dentro. Então, precisamos puxar aquele container:

let name = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name)

E finalmente podemos ler duas cordas para o .firstName e .lastName keys:

firstName = try name.decode(String.self, forKey: .firstName)lastName = try name.decode(String.self, forKey: .lastName)

Que termina o inicializador personalizado, mas ainda temos mais um método para escrever: encode(to:). Este é efetivamente o inverso do inicializador que acabamos de escrever, porque seu trabalho é converter nossas propriedades de volta em um container aninhado como apropriado.

Isso significa criar um container baseado em nosso CodingKeys enum e escrever age ali, depois criar um container aninhado baseado em nosso NameCodingKeys enum, e escrever tanto firstName quanto lastName ali:

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

Que completa todo o nosso código. Com isso no lugar, você pode agora ler diretamente a propriedade firstName, que é muito melhor!

Hacking with Swift é patrocinado por RevenueCat

SPONSORED Construir e manter a infra-estrutura de assinatura inapp é difícil. Felizmente, há uma maneira melhor. Com RevenueCat, você pode implementar assinaturas para seu aplicativo em horas, não em meses, para que você possa voltar a construir seu aplicativo.

Tente-o de graça

Sponsor Hacking com a Swift e alcançar a maior comunidade Swift do mundo!

Leave a Reply