Codable parece mágica à primeira vista. Você adiciona : Codable cinco letras a uma struct e a conversão para JSON surge de graça. Mas a mágica desaparece ao conectar uma API real: o servidor usa snake_case, enquanto Swift usa camelCase; os formatos de data variam por API; e um único item inválido pode fazer toda a decodificação falhar, deixando a tela vazia.
A sexta parte da série intermediária aprofunda Codable: o que a síntese realmente gera e as soluções padrão para quatro situações inevitáveis em produção: chaves, datas, aninhamento e falhas parciais.
A verdade por trás da mágica — código escrito pelo compilador
Codable é o typealias de Encodable & Decodable e dos protocolos que exigem saber como escrever a si próprio em um encoder e como criar a si próprio a partir de um decoder. O que parece mágica vem da síntese automática do compilador. Se todas as propriedades armazenadas forem Codable, o compilador gera duas coisas para você.
Primeiro, um enum CodingKeys: uma lista de chaves com cases que repetem os nomes das propriedades. Segundo, as implementações de init(from:) e encode(to:), que leem e escrevem cada propriedade usando essas chaves. Personalizar Codable é decidir qual desses dois resultados substituir manualmente. Se apenas as chaves diferem, use CodingKeys; se a estrutura também muda, implemente init(from:). Com esse modelo mental, todo o restante vira uma questão de quanto código escrever à mão.
Outro fato importante: Codable não é exclusivo de JSON. Como encoders e decoders podem ser substituídos, você pode usar PropertyListEncoder para plist ou bibliotecas de terceiros para XML e YAML. O tipo sabe apenas como se representar; o encoder decide o formato. Isso é separação de responsabilidades.
Situação 1. Nomes de chave diferentes — CodingKeys e estratégias de chave
Se o servidor envia user_name, mas você quer usar userName como propriedade, há duas etapas para a solução.
Se a regra for consistente globalmente, uma linha na configuração do decoder resolve: decoder.keyDecodingStrategy = .convertFromSnakeCase. Todas as chaves são convertidas automaticamente de snake_case para camelCase. Se toda a API segue essa convenção, essa é a resposta certa.
Se as regras forem inconsistentes ou você quiser renomear a chave, escreva CodingKeys manualmente.
struct User: Codable {
let userName: String
let signupDate: Date
enum CodingKeys: String, CodingKey {
case userName = "user_nm" // chave legada do servidor
case signupDate = "created"
}
}
Há um efeito colateral importante: remover um case de CodingKeys exclui a propriedade da codificação e da decodificação. Isso é útil para estados locais, como flags de cache, mas propriedades excluídas precisam ter valores padrão.
Situação 2. Datas — um campo minado de formatos
Date é a armadilha mais comum de Codable em produção. JSON não define um padrão único de data, então os servidores usam timestamps Unix (1720000000), ISO 8601 (“2026-07-15T09:30:00Z”) ou formatos personalizados (“2026-07-15 09:30”).
A solução é configurar dateDecodingStrategy de acordo com o formato do servidor: .secondsSince1970, .iso8601 ou .formatted(formatter) para um formato personalizado. Usar um DateFormatter personalizado tem duas armadilhas. Se o locale não for fixado em en_US_POSIX, o parsing pode falhar conforme a configuração de 12/24 horas do usuário. Além disso, .iso8601 falha quando o servidor envia ISO 8601 com milissegundos; ative a opção de milissegundos no ISO8601DateFormatter. A maioria dos bugs misteriosos em que o parsing de data falha apenas para alguns usuários vem dessas duas armadilhas.
Se os campos da mesma API usarem formatos diferentes, o caminho prático é receber apenas esse campo como String e convertê-lo em uma propriedade computada, ou ramificar com a estratégia .custom.
Situação 3. Aninhamento e estruturas incompatíveis — o formato do servidor versus o meu
É quando o JSON do servidor chega profundamente encapsulado, como em {"data": {"user": {...}}}. A solução mais simples é criar o tipo de envelope exatamente assim: espelhar a estrutura do servidor com struct Envelope: Codable { let data: DataBox } e extrair o valor no ponto de chamada usando envelope.data.user. É explícito e fácil de depurar, por isso é o melhor padrão para produção.
Se quiser um modelo diferente do servidor, como um User plano, implemente init(from:) manualmente e atravesse a hierarquia com nestedContainer. O código aumenta, mas o modelo fica mais limpo e centrado no domínio. O critério é o alcance de uso: um modelo central usado no app inteiro justifica um init(from:) escrito à mão; uma resposta de uma tela fica mais econômica com o espelhamento do envelope.
Situação 4. Falha parcial — quando um item derruba tudo
É a situação mais dolorosa em produção. Se um campo obrigatório for null em uma lista de 100 produtos, a decodificação do array inteiro lança um erro e a tela fica vazia. Como vimos na parte sobre tratamento de erros, erros lançados são propagados.
A primeira linha de defesa são os optionals. Declare honestamente como let thumbnail: URL? os campos que o servidor pode omitir. O princípio de registrar no tipo que algo “pode não existir” aplica-se diretamente ao design do modelo.
A defesa estrutural é um wrapper tolerante a falhas. Um padrão comum é criar um wrapper genérico que transforme falhas de decodificação de elementos em nil.
struct FailableItem<T: Decodable>: Decodable {
let value: T?
init(from decoder: Decoder) throws {
value = try? T(from: decoder) // nil em caso de falha nil
}
}
let items = try decoder.decode([FailableItem<Product>].self, from: data)
.compactMap(\.value) // manter apenas os que tiveram sucesso
É a combinação de try? e compactMap, as duas ferramentas apresentadas anteriormente. Ela expressa como um tipo a política de “descartar um item inválido e manter o restante”. Mas atenção: esse padrão engole as falhas silenciosamente. Em produção, registre a quantidade de falhas para que problemas nos dados do servidor não fiquem escondidos.
Depuração — DecodingError já sabe a resposta
Por fim, veja como investigar uma falha de decodificação. Capturar try decoder.decode(...) captura DecodingError, que é mais útil do que parece. Cada um dos quatro casos (keyNotFound, typeMismatch, valueNotFound e dataCorrupted) informa qual chave falhou, em qual codingPath, o que era esperado e o que chegou.
Quando a decodificação falhar, crie o hábito de inspecionar print(error) em vez de print(error as? DecodingError); pelo menos durante o desenvolvimento, imprima o codingPath por caso no bloco catch. Isso reduz muito o tempo de depuração. Na maioria dos casos de “não consigo analisar o JSON”, a resposta já está dentro do erro.
Resumo
- A mágica de Codable é a síntese do compilador. Ele escreve o enum CodingKeys e init(from:)/encode(to:) para você; personalizar significa decidir quanto substituir manualmente.
- Chaves: com snake_case consistente, uma linha de keyDecodingStrategy; com nomes irregulares, declare CodingKeys manualmente. Remover um case exclui o campo da conversão.
- Datas: especifique dateDecodingStrategy conforme o formato do servidor e tenha cuidado com o locale en_US_POSIX e com ISO 8601 com milissegundos.
- Aninhamento: por padrão, espelhe o envelope; nivele apenas os modelos principais com init(from:) + nestedContainer.
- Falha parcial: declaração honesta de optionals + padrão FailableItem (try? + compactMap), tornando visíveis por log as falhas engolidas.
- Depuração: a resposta está no codingPath de DecodingError.
Na próxima parte, veremos o princípio da sintaxe com arroba, como @State e @Published, e os property wrappers. Vamos criar a “sintaxe que envolve propriedades armazenadas” anunciada na parte sobre propriedades.

![Imagem de capa de [Swift intermediário #6] Codable avançado em Swift: guia para quatro armadilhas](/assets/images/posts/c82f52ea-3b48-40d5-8739-a94bd38731cf/swift-codable-deep-dive-1.jpg)