Ao usar os protocolos Swift4 e Codable, tive o seguinte problema - parece que não há como permitir JSONDecoder
pular elementos em uma matriz. Por exemplo, tenho o seguinte JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
E uma estrutura codificável :
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Ao decodificar este json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
O resultado products
está vazio. O que era de se esperar, devido ao fato de que o segundo objeto em JSON não tem "points"
chave, enquanto points
não é opcional em GroceryProduct
struct.
A questão é como posso permitir JSONDecoder
"pular" um objeto inválido?
points
simplesmente ser declarado opcional?Respostas:
Uma opção é usar um tipo de wrapper que tenta decodificar um determinado valor; armazenar
nil
se malsucedido:Podemos então decodificar uma matriz destes, com o seu
GroceryProduct
preenchimento noBase
espaço reservado:Em seguida, estamos usando
.compactMap { $0.base }
para filtrar osnil
elementos (aqueles que geraram um erro na decodificação).Isso criará um array intermediário de
[FailableDecodable<GroceryProduct>]
, o que não deve ser um problema; no entanto, se quiser evitá-lo, você sempre pode criar outro tipo de wrapper que decodifique e desembrulhe cada elemento de um contêiner sem chave:Você então decodificaria como:
fonte
var container = try decoder.unkeyedContainer()
init(from:) throws
, então o Swift propagará automaticamente o erro de volta para o chamador (neste caso, o decodificador, que o propagará de volta para aJSONDecoder.decode(_:from:)
chamada).Eu criaria um novo tipo
Throwable
, que pode envolver qualquer tipo em conformidade comDecodable
:Para decodificar um array de
GroceryProduct
(ou qualquer outroCollection
):onde
value
é uma propriedade computada introduzida em uma extensão emThrowable
:Eu optaria por usar um
enum
tipo de wrapper (em vez de aStruct
) porque pode ser útil controlar os erros que são lançados, bem como seus índices.Swift 5
Para Swift 5, considere usar o exemplo
Result
enum
Para desembrulhar o valor decodificado, use o
get()
método naresult
propriedade:fonte
init
O problema é que, ao iterar em um contêiner, o container.currentIndex não é incrementado para que você possa tentar decodificar novamente com um tipo diferente.
Como o currentIndex é somente leitura, uma solução é incrementá-lo você mesmo, decodificando com sucesso um fictício. Peguei a solução @Hamish e escrevi um wrapper com um init personalizado.
Este problema é um bug atual do Swift: https://bugs.swift.org/browse/SR-5953
A solução postada aqui é uma solução alternativa em um dos comentários. Gosto dessa opção porque estou analisando vários modelos da mesma maneira em um cliente de rede e queria que a solução fosse local para um dos objetos. Ou seja, ainda quero que os outros sejam descartados.
Eu explico melhor no meu github https://github.com/phynet/Lossy-array-decode-swift4
fonte
if/else
, uso umdo/catch
dentro dowhile
loop para registrar o erroExistem duas opções:
Declara todos os membros da estrutura como opcionais, cujas chaves podem estar ausentes
Escreva um inicializador personalizado para atribuir valores padrão no
nil
caso.fonte
try?
comdecode
, é melhor usartry
com adecodeIfPresent
segunda opção. Precisamos definir o valor padrão apenas se não houver chave, não no caso de qualquer falha de decodificação, como quando a chave existe, mas o tipo está errado.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
isso, se falhar, ele apenas colocará 0000, mas ainda assim falhará.decodeIfPresent
está erradoAPI
porque a chave existe. Use outrodo - catch
bloco. DecodifiqueString
, se ocorrer um erro, decodifiqueInt
Uma solução possibilitada pelo Swift 5.1, usando o wrapper de propriedade:
E então o uso:
Nota: As coisas do wrapper de propriedade só funcionarão se a resposta puder ser encapsulada em uma estrutura (ou seja: não em uma matriz de nível superior). Nesse caso, você ainda pode envolvê-lo manualmente (com um typealias para melhor legibilidade):
fonte
Eu coloquei a solução @sophy-swicz, com algumas modificações, em uma extensão fácil de usar
Basta chamá-lo assim
Para o exemplo acima:
fonte
Infelizmente Swift 4 API não tem initializer failable para
init(from: Decoder)
.Apenas uma solução que vejo é implementar a decodificação personalizada, fornecendo valor padrão para campos opcionais e possível filtro com os dados necessários:
fonte
Tive um problema semelhante recentemente, mas um pouco diferente.
Nesse caso, se um dos elementos em
friendnamesArray
for nulo, todo o objeto será nulo durante a decodificação.E a maneira certa de lidar com esse caso extremo é declarar o array de strings
[String]
como um array de strings opcionais[String?]
conforme abaixo,fonte
Eu melhorei o @Hamish's para o caso, que você deseja este comportamento para todos os arrays:
fonte
A resposta de @Hamish é ótima. No entanto, você pode reduzir
FailableCodableArray
para:fonte
Em vez disso, você também pode fazer assim:
e, em seguida, ao obtê-lo:
fonte
Eu vim com isso
KeyedDecodingContainer.safelyDecodeArray
que fornece uma interface simples:O loop potencialmente infinito
while !container.isAtEnd
é uma preocupação e é abordado usandoEmptyDecodable
.fonte
Uma tentativa muito mais simples: por que você não declara pontos como opcionais ou faz com que a matriz contenha elementos opcionais
fonte