Swift 4 adicionou o novo Codable
protocolo. Quando eu uso JSONDecoder
, parece exigir que todas as propriedades não opcionais da minha Codable
classe tenham chaves no JSON ou ele gera um erro.
Tornar todas as propriedades da minha classe opcionais parece um aborrecimento desnecessário, pois o que eu realmente quero é usar o valor no json ou um valor padrão. (Não quero que a propriedade seja nula.)
Existe uma maneira de fazer isso?
class MyCodable: Codable {
var name: String = "Default Appleseed"
}
func load(input: String) {
do {
if let data = input.data(using: .utf8) {
let result = try JSONDecoder().decode(MyCodable.self, from: data)
print("name: \(result.name)")
}
} catch {
print("error: \(error)")
// `Error message: "Key not found when expecting non-optional type
// String for coding key \"name\""`
}
}
let goodInput = "{\"name\": \"Jonny Appleseed\" }"
let badInput = "{}"
load(input: goodInput) // works, `name` is Jonny Applessed
load(input: badInput) // breaks, `name` required since property is non-optional
Respostas:
A abordagem que eu prefiro é usar os chamados DTOs - objeto de transferência de dados. É uma estrutura que está em conformidade com Codable e representa o objeto desejado.
Em seguida, você simplesmente inicia o objeto que deseja usar no aplicativo com esse DTO.
Essa abordagem também é boa, pois você pode renomear e alterar o objeto final como desejar. É claro e requer menos código do que a decodificação manual. Além disso, com essa abordagem, você pode separar a camada de rede de outro aplicativo.
fonte
Você pode implementar o
init(from decoder: Decoder)
método em seu tipo em vez de usar a implementação padrão:Você também pode fazer
name
uma propriedade constante (se quiser):ou
Re seu comentário: Com uma extensão personalizada
você pode implementar o método init como
mas isso não é muito mais curto do que
fonte
CodingKeys
enumeração gerada automaticamente (para remover a definição personalizada) :)ObjectMapper
lida com isso muito bem.Decodable
e também fornecendo sua própria implementaçãoinit(from:)
? Nesse caso, o compilador assume que você deseja manipular a decodificação manualmente e, portanto, não sintetiza umCodingKeys
enum para você. Como você disse, conformar-se comCodable
funciona porque agora o compilador está sintetizandoencode(to:)
para você e, portanto, também sintetizaCodingKeys
. Se você também fornecer sua própria implementação deencode(to:)
,CodingKeys
não será mais sintetizado.Uma solução seria usar uma propriedade computada cujo padrão é o valor desejado se a chave JSON não for encontrada. Isso adiciona um pouco de verbosidade extra, pois você precisará declarar outra propriedade e exigirá a adição do
CodingKeys
enum (se ainda não estiver lá). A vantagem é que você não precisa escrever um código de decodificação / codificação personalizado.Por exemplo:
fonte
Você pode implementar.
fonte
Se você não deseja implementar seus métodos de codificação e decodificação, existe uma solução um tanto suja em torno dos valores padrão.
Você pode declarar seu novo campo como opcional desempacotado implicitamente e verificar se ele é nulo após a decodificação e definir um valor padrão.
Eu testei isso apenas com PropertyListEncoder, mas acho que JSONDecoder funciona da mesma maneira.
fonte
Me deparei com essa pergunta procurando exatamente a mesma coisa. As respostas que encontrei não foram muito satisfatórias, embora eu tivesse medo de que as soluções aqui fossem a única opção.
No meu caso, a criação de um decodificador customizado exigiria uma tonelada de clichês que seria difícil de manter, então continuei procurando outras respostas.
Encontrei este artigo que mostra uma maneira interessante de superar isso em casos simples usando um
@propertyWrapper
. O mais importante para mim era que ele era reutilizável e exigia um mínimo de refatoração do código existente.O artigo presume um caso em que você deseja que uma propriedade booleana ausente seja padronizada como false sem falhar, mas também mostra outras variantes diferentes. Você pode ler com mais detalhes, mas mostrarei o que fiz para o meu caso de uso.
No meu caso, eu tinha um
array
que queria inicializar como vazio se a chave estivesse faltando.Portanto, declarei as seguintes
@propertyWrapper
e outras extensões:A vantagem desse método é que você pode facilmente superar o problema no código existente simplesmente adicionando o
@propertyWrapper
à propriedade. No meu caso:Espero que isso ajude alguém a lidar com o mesmo problema.
ATUALIZAR:
Depois de postar esta resposta e continuar a investigar o assunto, encontrei este outro artigo, mas o mais importante, a respectiva biblioteca que contém alguns s comuns fáceis de usar
@propertyWrapper
para esses tipos de casos:https://github.com/marksands/BetterCodable
fonte
Se você acha que escrever sua própria versão do
init(from decoder: Decoder)
é opressor, recomendo que implemente um método que verificará a entrada antes de enviá-la para o decodificador. Dessa forma, você terá um local onde poderá verificar a ausência de campos e definir seus próprios valores padrão.Por exemplo:
E para iniciar um objeto de json, em vez de:
Init será parecido com este:
Nesta situação particular, eu prefiro lidar com opcionais, mas se você tiver uma opinião diferente, você pode tornar seu método customDecode (:) jogável
fonte