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
.
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 Codable
Os 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!
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