Mientras usaba los protocolos Swift4 y Codable, tuve el siguiente problema: parece que no hay forma de permitir JSONDecoder
omitir elementos en una matriz. Por ejemplo, tengo el siguiente JSON:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
Y una estructura codificable :
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Al decodificar este json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
El resultado products
está vacío. Lo cual es de esperar, debido al hecho de que el segundo objeto en JSON no tiene "points"
clave, mientras points
que no es opcional enGroceryProduct
struct.
La pregunta es ¿cómo puedo permitir JSONDecoder
"omitir" un objeto no válido?
points
puede declarar simplemente opcional?Respuestas:
Una opción es utilizar un tipo de contenedor que intente decodificar un valor dado; almacenar
nil
si no tiene éxito:Luego podemos decodificar una serie de estos, con su
GroceryProduct
relleno en elBase
marcador de posición:Luego estamos usando
.compactMap { $0.base }
para filtrarnil
elementos (aquellos que arrojaron un error en la decodificación).Esto creará una matriz intermedia de
[FailableDecodable<GroceryProduct>]
, que no debería ser un problema; sin embargo, si desea evitarlo, siempre puede crear otro tipo de contenedor que decodifique y desenvuelva cada elemento de un contenedor sin clave:Luego decodificaría como:
fuente
var container = try decoder.unkeyedContainer()
init(from:) throws
, por lo que Swift propagará automáticamente el error a la persona que llama (en este caso, el decodificador, que lo propagará de regreso a laJSONDecoder.decode(_:from:)
llamada).Crearía un nuevo tipo
Throwable
, que puede envolver cualquier tipo conforme aDecodable
:Para decodificar una matriz de
GroceryProduct
(o cualquier otraCollection
):donde
value
es una propiedad calculada introducida en una extensión enThrowable
:Optaría por usar un
enum
tipo de envoltorio (sobre unStruct
) porque puede ser útil para realizar un seguimiento de los errores que se producen, así como de sus índices.Rápido 5
Para Swift 5, considere usar el ejemplo
Result
enum
Para desenvolver el valor decodificado, use el
get()
método de laresult
propiedad:fuente
init
El problema es que cuando se itera sobre un contenedor, el contenedor.currentIndex no se incrementa, por lo que puede intentar decodificar nuevamente con un tipo diferente.
Debido a que currentIndex es de solo lectura, una solución es incrementarlo usted mismo decodificando exitosamente un maniquí. Tomé la solución @Hamish y escribí un contenedor con un init personalizado.
Este problema es un error actual de Swift: https://bugs.swift.org/browse/SR-5953
La solución publicada aquí es una solución alternativa en uno de los comentarios. Me gusta esta opción porque estoy analizando un montón de modelos de la misma manera en un cliente de red y quería que la solución fuera local para uno de los objetos. Es decir, todavía quiero que se descarten los demás.
Explico mejor en mi github https://github.com/phynet/Lossy-array-decode-swift4
fuente
if/else
usodo/catch
dentro delwhile
ciclo para poder registrar el errorHay dos opciones:
Declare todos los miembros de la estructura como opcionales cuyas claves pueden faltar
Escriba un inicializador personalizado para asignar valores predeterminados en el
nil
caso.fuente
try?
condecode
, es mejor usartry
condecodeIfPresent
en la segunda opción. Necesitamos establecer el valor predeterminado solo si no hay una clave, no en caso de falla de decodificación, como cuando existe una clave, pero el tipo es incorrecto.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
así que si falla, solo pondrá 0000 pero aún falla.decodeIfPresent
está malAPI
porque la clave existe. Usa otrodo - catch
bloque. DecodificarString
, si ocurre un error, decodificarInt
Una solución hecha posible por Swift 5.1, utilizando el contenedor de propiedades:
Y luego el uso:
Nota: Las cosas del contenedor de propiedades solo funcionarán si la respuesta se puede empaquetar en una estructura (es decir, no en una matriz de nivel superior). En ese caso, aún puede ajustarlo manualmente (con un tipo alias para una mejor legibilidad):
fuente
He puesto la solución @ sophy-swicz, con algunas modificaciones, en una extensión fácil de usar
Solo llámalo así
Para el ejemplo anterior:
fuente
Desafortunadamente, la API de Swift 4 no tiene un inicializador fallido para
init(from: Decoder)
.Solo una solución que veo es implementar la decodificación personalizada, dando un valor predeterminado para los campos opcionales y un posible filtro con los datos necesarios:
fuente
Recientemente tuve un problema similar, pero un poco diferente.
En este caso, si uno de los elementos en
friendnamesArray
es nulo, todo el objeto es nulo mientras se decodifica.Y la forma correcta de manejar este caso de borde es declarar la matriz de cadenas
[String]
como una matriz de cadenas opcionales[String?]
como se muestra a continuación,fuente
Mejoré en @ Hamish para el caso, que desea este comportamiento para todas las matrices:
fuente
La respuesta de @ Hamish es genial. Sin embargo, puede reducirlo
FailableCodableArray
a:fuente
En su lugar, también puede hacer esto:
y luego adentro mientras lo obtengo:
fuente
Se me ocurre esto
KeyedDecodingContainer.safelyDecodeArray
que proporciona una interfaz simple:El bucle potencialmente infinito
while !container.isAtEnd
es una preocupación y se aborda mediante el uso deEmptyDecodable
.fuente
Un intento mucho más simple: ¿Por qué no declaras los puntos como opcionales o haces que la matriz contenga elementos opcionales?
fuente