Written by Reinder de Vries on January 20 2021 in App Development, iOS, Swift
>
Pode usar Codable
no Swift para codificar e descodificar formatos de dados personalizados, tais como JSON, para objectos Swift nativos. É incrivelmente fácil mapear objetos Swift para dados JSON, e vice-versa, simplesmente adotando o protocolo Codable
.
Como um desenvolvedor iOS pragmático, você se deparará com JSON mais cedo do que mais tarde. Cada webservice, do Facebook ao Foursquare, usa o formato JSON para obter dados para dentro e para fora do seu aplicativo. Como você pode codificar e decodificar esses dados JSON para objetos Swift efetivamente?
Neste tutorial você aprenderá como trabalhar com objetos JSON no Swift, usando o protocolo Codable
. O que você vai aprender, pode ser facilmente aplicado a outros formatos de dados também. Vamos entrar na codificação e descodificação com JSONEncoder
e JSONDecoder
, e vou mostrar-lhe como mediar entre os dados JSON e Swift.
Ready? Vamos lá.
- Comece: Codificação e Decodificação
- O Protocolo Codificável Explicado
- Trabalho com Codificável
- Descodificação JSON para Swift Objectos com Codificável
- Codificação Swift Objectos como JSON com Codificável
- Trabalho com Matrizes Aninhadas e Codificável
- Outras Leituras
>
>
>
Comece: Codificação e Decodificação
Que problema o protocolo Codable
em Swift realmente resolve? Vamos começar com um exemplo.
Imagine que você está construindo uma aplicação de receita. O aplicativo mostra várias receitas em uma lista, incluindo os ingredientes, instruções e informações básicas sobre alimentos.
Você obtém os dados para o aplicativo de um webservice, e sua API baseada em nuvem. Esta API usa o formato de dados JSON. Toda vez que você solicita uma receita do webservice, você obtém os dados JSON de volta.
Aqui está um exemplo de dados JSON para uma receita:
{ "name": "Spaghetti Bolognese", "author": "Reinder's Cooking Corner", "url": "https://cookingcorner.com/spaghetti-bolognese", "yield": 4, "ingredients": , "instructions": "Cook spaghetti, fry beef and garlic, add tomatoes, add love, eat!"}
Dê uma olhada na estrutura dos dados JSON.
Os objetos JSON são envolvidos entre parênteses rectos {
e }
, e as arrays são envolvidas entre parênteses rectos . Nomes de propriedades são cordas, envoltas entre aspas
"
. Os valores no JSON podem ser cadeias, números (sem aspas), matrizes ou outros objetos. Você também pode aninhar dados, ou seja, arrays em arrays, objetos em arrays, etc., para criar uma hierarquia complexa de dados.
JSON é um formato de dados baseado em texto que muitos serviços web usam, incluindo APIs do Twitter, Facebook, Foursquare, e assim por diante. Se você está construindo aplicativos que usam recursos baseados na web, você vai encontrar JSON.
O formato JSON é superior ao XML, uma alternativa comum, porque é eficiente, facilmente analisável e legível por humanos. JSON é um formato acordado para webservices, APIs e aplicativos. É usado em toda a web, aplicativos e serviços online, porque o formato é simples e flexível.
E JSON tem uma capacidade soberba: você pode codificar qualquer formato de dados em JSON, e decodificar JSON de volta para qualquer formato de dados. Esse processo de codificação e decodificação é o que torna o JSON tão poderoso.
Você pode pegar seus valores Swift String
, Int
, Double
, URL
, Date
, Data
, Array
e Dictionary
, e codificá-los como JSON. Você então os envia para o webservice, que decodifica os valores em um formato nativo que ele entende. Da mesma forma, o webservice envia dados codificados como JSON para seu aplicativo, e você decodifica os dados para tipos nativos como String
, Double
e Array
.
Quando seu aplicativo de receita recebe o JSON (veja acima), ele pode então ser decodificado em uma estrutura Swift, como esta:
struct Recipe { var name: String var author: String var url: URL var yield: Int var ingredients: var instructions: String}
Na Swift, o protocolo Codable
é usado para ir de um objeto de dados JSON para uma classe ou estrutura Swift real. Isto é chamado de decodificação, porque os dados JSON são decodificados em um formato que a Swift entende. Também funciona da outra forma: codificando objectos Swift como JSON.
O protocolo Codable
no Swift é na verdade um alias dos protocolos Decodable
e Encodable
. Como você frequentemente usa codificação e decodificação juntos, você usa o protocolo Codable
para obter ambos os protocolos de uma só vez.
A peça central do fluxo de trabalho de codificação/descodificação é o protocolo Codable
do Swift. Vamos descobrir como funciona, a seguir!
Não pode dizer “codificação” e “descodificação” separados? Pense nisto assim: Estamos a converter dados de e para “código”, como uma máquina Enigma ou uma cifra secreta de criptografia. Codificar significa converter dados em código; en-coding, ou “in/within code”. Descodificar significa converter código em dados; descodificar, ou “de/fora código”.
Contratado como desenvolvedor iOS
Aprenda a construir aplicações iOS 14 com Swift 5
Inscreva-se no meu curso de desenvolvimento iOS, e aprenda como começar a sua carreira como desenvolvedor profissional iOS.
O Protocolo Codificável Explicado
Usar o JSON antes do Swift 4 era um pouco de PITA. Você mesmo teria que serrar o JSON com JSONSerialization
, e depois digitar todas as propriedades do JSON para a direita tipo Swift.
let json = try? JSONSerialization.jsonObject(with: data, options: )if let recipe = json as? { if let yield = recipe as? Int { recipeObject.yield = yield }}
O snippet acima só pega um valor de yield
do JSON, e digita para Int
. É extremamente verboso, e é difícil responder adequadamente a potenciais erros e discrepâncias de tipo. Mesmo que funcione, não é ideal.
Bibliotecas como SwiftyJSON tornam o trabalho com JSON muito mais fácil, mas você ainda precisa mapear os dados JSON para seus respectivos objetos e propriedades Swift.
Com Swift 4, e mais tarde, você pode usar o protocolo Codable
em seu lugar. Sua estrutura Swift ou classe tem apenas que adotar o protocolo Codable
, e você obtém a codificação e decodificação JSON fora da caixa, de graça.
O protocolo Codable
é uma composição de dois protocolos, Decodificável e Codificável. Ambos os protocolos são bastante mínimos; eles apenas definem as funções init(from: Decoder)
e encode(to: Encoder)
, respectivamente. Em outras palavras, essas funções significam que para um tipo ser “decodificável” ou “codificável”, eles precisarão “decodificar de algo” e “codificar para algo”.
A verdadeira magia de Codable
acontece com os protocolos Decoder
e Encoder
. Estes protocolos são adoptados pelos componentes que codificam/decodificam vários formatos de dados, como o JSON. No Swift, temos alguns -coders:
-
PropertyListEncoder
ePropertyListDecoder
para listas de propriedades .plist -
JSONEncoder
eJSONDecoder
para o JSON – somos nós! - NSKeyedArchiver pode trabalhar com
Codable
, viaPropertyListEncoder
,Data
eNSCoding
As classes JSONDecoder
e JSONEncoder
usam os protocolos Decoder
e Encoder
para fornecer a funcionalidade para decodificar/codificar JSON. Isso também significa que você pode escrever seu próprio codificador/descodificador personalizado para Codable
, desde que você adote os protocolos Decoder
e Encoder
!
>
Need a refresh on protocols in Swift? Leia os protocolos em Swift Explicado para alcançar.
Trabalhar com codificadores
Vejamos um exemplo. Vamos mapear alguns dados do JSON para uma estrutura Swift. Estamos essencialmente descodificando o JSON para um objeto Swift real.
Primeiro, estamos criando uma estrutura chamada User
. Assim:
struct User: Codable { var first_name: String var last_name: String var country: String}
A estrutura User
tem três propriedades simples do tipo String
, e está de acordo com o protocolo Codable
.
Então, vamos escrever um pouco de JSON. Este é o JSON com o qual vamos trabalhar:
{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}
JSON dados normalmente entram no seu aplicativo como resposta de um pedido de webservice, ou através de um arquivo .json
, mas para este exemplo nós simplesmente colocamos o JSON em um arquivo String
. Assim:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Note: O código acima usa a citação tripla """
para criar cadeias de caracteres de várias linhas. Neat!
O que estamos fazendo a seguir, é decodificar o JSON e transformá-lo em um objeto User
. Assim:
let jsonData = jsonString.data(using: .utf8)!let user = try! JSONDecoder().decode(User.self, from: jsonData)print(user.last_name)// Output: Doe
Aqui o que acontece:
- Primeiro, você transforma
jsonString
em um objetoData
chamando a funçãodata(using:)
na string. Este é um passo intermediário necessário. - Então, você cria um objeto
JSONDecoder
e imediatamente chama a funçãodecode(_:from:)
sobre ele. Isto transforma ojsonData
em um objeto do tipoUser
, decodificando o JSON. O tipoUser.self
é fornecido como um parâmetro. - Finalmente, você imprime o sobrenome do usuário com
print(user.last_name)
. Esseuser
valor temUser
como tipo, então é um objeto Swift real!
Easy, certo? Você essencialmente “mapeou” o objeto JSON para uma estrutura Swift, e decodificou o formato JSON para um objeto nativo Swift pode trabalhar com.
No código acima, estamos ignorando qualquer erro lançado por decode(_:from:)
com try!
. No seu próprio código, você precisará lidar com erros com um bloco de do-try-catch
. Um erro que pode ser lançado, por exemplo, vem de fornecer JSON.
O User.self
é um metatype, uma referência ao próprio tipo User
. Estamos dizendo ao decodificador que queremos decodificar os dados com a estrutura User
, fornecendo uma referência a esse tipo.
Um erro comum ao definir seu tipo Codable
, como a estrutura User
, é um desajuste de chaves e propriedades. Se você adicionou uma propriedade à sua estrutura, que não está presente no JSON ou tem um tipo diferente, você receberá um erro ao decodificar o JSON. O contrário, ou seja, uma propriedade que não existe na sua estrutura mas que existe no JSON, não é um problema. É mais fácil depurar esta propriedade de cada vez, ou seja, continuar adicionando propriedades até que o JSON não decodifique mais sem erros. Você também pode determinar quais propriedades/chaves devem ser incluídas ou ignoradas com o CodingKeys
enum (veja abaixo).
Decodificando o JSON para Swift Objects com Codificável
Vejamos novamente o exemplo da seção anterior, e o expandamos. Aqui está o JSON com o qual estamos trabalhando:
let jsonString = """{ "first_name": "John", "last_name": "Doe", "country": "United Kingdom"}"""
Fazemos então um objeto Data
. Assim:
let jsonData = jsonString.data(using: .utf8)!
Este é um passo intermediário necessário. Em vez de representar o JSON como uma string, agora armazenamos o JSON como um objeto nativo Data
. A codificação de caracteres que estamos usando para essa string é UTF8.
Se você olhar atentamente, você verá que o código acima usa o desembrulhamento de força para trabalhar com o valor de retorno opcional de data(using:)
. Vamos desembrulhar essa opção mais elegantemente!
if let jsonData = jsonString.data(using: .utf8) { // Use `jsonData`} else { // Respond to error }
Com o código acima podemos responder a erros quando data(using:)
retorna nil
. Você pode, por exemplo, mostrar uma mensagem de erro, ou silenciosamente deixar a tarefa falhar e salvar informações de diagnóstico em um log.
Próximo, estamos criando um novo objeto JSONDecoder
. Assim:
let decoder = JSONDecoder()
Usamos então esse descodificador para descodificar os dados JSON. Assim:
let user = try! decoder.decode(User.self, from: jsonData)
No entanto, a função decode(_:from:)
pode lançar erros. O código acima trava sempre que isso acontece. Mais uma vez, queremos responder a quaisquer erros que possam acontecer. Assim:
do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name)} catch { print(error.localizedDescription)}
E todo o bloco de código agora se parece com o seguinte. Veja como isso é diferente?
if let jsonData = jsonString.data(using: .utf8){ let decoder = JSONDecoder() do { let user = try decoder.decode(User.self, from: jsonData) print(user.last_name) } catch { print(error.localizedDescription) }}
Nunca silenciar erros. Pegue o erro e responda a ele, seja com UI/UX, tentando novamente a tarefa, ou registrando, travando e corrigindo-o.
Trabalhar com CodingKeys
E se as nossas propriedades JSON, como first_name
e/ou firstName
, são diferentes das propriedades de estrutura Swift? É aí que CodingKeys
entra.
Todas as classes ou estruturas que estejam de acordo com Codable
podem declarar uma enumeração especial aninhada chamada CodingKeys
. Você a usa para definir propriedades que devem ser codificadas e decodificadas, incluindo seus nomes.
Vamos dar uma olhada em um exemplo. Na estrutura User
abaixo, mudamos os nomes das propriedades de snake_case para camelCase. Por exemplo, a chave first_name
é agora chamada firstName
.
struct User:Codable { var firstName: String var lastName: String var country: String enum CodingKeys: String, CodingKey { case firstName = "first_name" case lastName = "last_name" case country }}
Quando utilizar a estrutura acima com os exemplos das secções anteriores, verá que pode utilizar um objecto User
com os novos nomes das propriedades. Assim:
print(user.firstName)// Output: Johnprint(user.country)// Output: United Kingdom
A CodingKeys
enumeração mapeia os seus casos para propriedades, e usa os valores da string para seleccionar os nomes correctos das propriedades nos dados do JSON. O caso no enumero é o nome da propriedade que você quer usar na estrutura, e seu valor é o nome da chave nos dados JSON.
Codificando Objetos Swift como JSON com Codificável
Até agora nos concentramos em decodificar dados JSON para objetos Swift. E que tal ir para o outro lado? Também podemos codificar objectos como JSON? Sim, porque não!
var user = User()user.firstName = "Bob"user.lastName = "and Alice"user.country = "Cryptoland"let jsonData = try! JSONEncoder().encode(user)let jsonString = String(data: jsonData, encoding: .utf8)!print(jsonString)
E a saída é:
{"country":"Cryptoland","first_name":"Bob","last_name":"and Alice"}
O que acontece aqui?
- Primeiro, você cria um objeto
User
e atribui alguns valores às suas propriedades - Então, você usa
encode(_:)
para codificar o objetouser
para um JSONData
objeto - Finalmente, você converte o objeto
Data
paraString
e imprime-o
Se você olhar atentamente, você verá que a codificação segue os mesmos passos da decodificação, exceto ao contrário. Passando do objecto Swift, através do descodificador, resultando numa string JSON.
Just as before, podemos expandir o exemplo para lidar com os erros? Sim! Assim:
let encoder = JSONEncoder()encoder.outputFormatting = .prettyPrinteddo { let jsonData = try encoder.encode(user) if let jsonString = String(data: jsonData, encoding: .utf8) { print(jsonString) }} catch { print(error.localizedDescription)}
No exemplo acima, também estamos usando a propriedade outputFormatting
do codificador para “imprimir bem” os dados JSON. Isto adiciona espaços, abas e novas linhas para tornar a string JSON mais fácil de ler. Assim:
{ "country" : "Cryptoland", "first_name" : "Bob", "last_name" : "and Alice"}
Awesome!
Vale a pena notar que os dicionários em Swift, e em JSON, não têm uma ordem de ordenação fixa. É por isso que você verá várias ordens de ordenação para as chaves country
, first_name
, etc. no JSON, se você executar o código acima algumas vezes. A maioria dos webservices que saem do JSON irão impor uma ordem de ordenação fixa através de um esquema, o que definitivamente facilita a leitura e navegação do JSON.
Trabalhando com Arrays Aninhados e Codificáveis
Até agora temos trabalhado com objetos JSON simples – apenas uma classe com algumas propriedades. Mas e se os dados com os quais você está trabalhando forem mais complexos?
Por exemplo, os dados JSON que você pode obter de um serviço web do Twitter ou Facebook são frequentemente aninhados. Ou seja, os dados que você quer obter são enterrados em 2-3 níveis de arrays e dicionários JSON. E agora!?
Vejamos um exemplo simples, primeiro. Aqui está a estrutura Swift com que vamos trabalhar:
struct User: Codable { var first_name: String var last_name: String}
Então, aqui estão os dados JSON que queremos descodificar:
let jsonString = """"""
Se você olhar com atenção, você vê que os dados que precisamos são armazenados como um array. O JSON não é um objecto simples, mas é um array com 3 User
objectos. Sabemos que é um array de objetos, por causa dos colchetes (array) e colchetes com quadrados
{ }
(objeto). Múltiplos itens são sempre separados por vírgulas.
Como podemos descodificar estes User
objectos? Aqui está como:
let jsonData = jsonString.data(using: .utf8)!let users = try! JSONDecoder().decode(.self, from: jsonData)for user in users { print(user.first_name)}
O trecho de código acima é extremamente similar ao que você já viu antes, mas há uma diferença importante. Veja o tipo que estamos fornecendo para a função decode(_:from:)
? O tipo é (com .self), ou “array of User objects”. Ao invés de trabalhar com um objeto
User
, nós queremos decodificar um monte deles, e isso é designado com o tipo array.
Nexterior, vamos discutir como você pode trabalhar com tipos aninhados mais complexos. Considere, por exemplo, que o array de objetos User
está aninhado dentro de um dicionário JSON. Assim:
let jsonString = """{ "users": }"""
No trecho acima do JSON, o elemento de nível superior é um dicionário (ou “objeto”). Ele tem apenas um par de valores-chave: a users
array. Os dados que queremos estão dentro desse array. Como chegamos a ele?
É importante não complicar muito a nossa abordagem. É fácil pensar que você vai precisar de alguma análise avançada do JSON, mas como acontece, nós também podemos aninhar estruturas Swift. Vamos descrever o JSON inteiro como uma estrutura, com a estrutura User
dentro dele.
Aqui, veja isto:
struct Response: Codable{ struct User: Codable { var first_name: String var last_name: String } var users: }
Aqui está o que está acontecendo:
- O dicionário JSON de nível superior corresponde ao tipo
Response
. Tal como antes, este tipo corresponde aCodable
, para suportar a codificação e descodificação JSON. - Dentro dessa estrutura definimos outra estrutura chamada
User
. Este é exatamente o mesmo tipo que já usamos antes. Esta estrutura é “aninhada”. - A estrutura
Response
tem uma propriedade chamadausers
do tipo, ou array de objetos User.
O legal é que semanticamente, ambas as estruturas de dados JSON e Swift são exatamente as mesmas. Elas apenas usam uma sintaxe diferente. Nós definimos um array aninhado dentro do dicionário de nível superior, assim como a estrutura Response
tem uma estrutura aninhada User
estrutura e users
propriedade dentro dela.
Fazê-la funcionar é canja agora. Veja isto:
let jsonData = jsonString.data(using: .utf8)!let response = try! JSONDecoder().decode(Response.self, from: jsonData)for user in response.users { print(user.first_name)}
Veja como estamos usando o tipo Response
para decodificar o JSON, e depois faça um loop sobre a propriedade response.users
? Verifique isso com a estrutura Response
, e o JSON. Estamos escolhendo o par users
chave-valor no dicionário de nível superior, e mapeamos os objetos dentro para User
objetos. Neat!
Usar tipos aninhados é uma ótima abordagem de propósito geral para estruturas de dados complexos do JSON. Quando você se deparar com um pouco de JSON você não pode facilmente representar em um tipo tangível, como User
ou Tweet
, tente expandir o tipo para algo como Response
ou UserCollection
. Em vez de ir mais fundo, vá mais longe, e incorpore o JSON completo. Use tipos aninhados para chegar aos dados que você deseja. Também vale a pena notar aqui que você não precisa adicionar o User
structure no Response
struct – ele também pode ir para fora dele.
Contratado como desenvolvedor iOS
Aprenda a construir aplicações iOS 14 com Swift 5
Inscreva-se no meu curso de desenvolvimento iOS, e aprenda como começar a sua carreira como desenvolvedor profissional iOS.
Leitura adicional
E isso é tudo! Agora você já sabe:
- Como usar
Codable
para criar objectos que podem ser codificados e descodificados - Como usar
JSONDecoder
eJSONEncoder
para codificar e descodificar objectos JSON e seus equivalentes Swift - Para que serve codificar e descodificar, e porque é importante no desenvolvimento diário do iOS
- Como trabalhar com dados JSON mais complexos, e como usar tipos aninhados
Quer saber mais? Veja estes recursos:
- Arrays, Dicionários e Estruturas
- Trabalhar com JSON em Swift com SwiftyJSON
- Introdução de Programação Orientada a Objetos em Swift
- Como fazer: Find An Item In An Array In Swift
- How To Use Apple’s Developer Documentation For Fun And Profit
- How To Find Strings With Regular Expressions In Swift
- Why App Architecture Matters
- Strings in Swift Explained
- Working with Files on iOS with Swift
- Storing Data with NSCoding and NSKeyedArchiver