Mientras usaba los protocolos Swift4 y Codable, tuve el siguiente problema: parece que no hay forma de permitir JSONDecoderomitir 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 productsestá vacío. Lo cual es de esperar, debido al hecho de que el segundo objeto en JSON no tiene "points"clave, mientras pointsque no es opcional enGroceryProduct struct.
La pregunta es ¿cómo puedo permitir JSONDecoder"omitir" un objeto no válido?

pointspuede declarar simplemente opcional?Respuestas:
Una opción es utilizar un tipo de contenedor que intente decodificar un valor dado; almacenar
nilsi no tiene éxito:Luego podemos decodificar una serie de estos, con su
GroceryProductrelleno en elBasemarcador de posición:Luego estamos usando
.compactMap { $0.base }para filtrarnilelementos (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
valuees una propiedad calculada introducida en una extensión enThrowable:Optaría por usar un
enumtipo 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
ResultenumPara desenvolver el valor decodificado, use el
get()método de laresultpropiedad:fuente
initEl 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/elseusodo/catchdentro delwhileciclo 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
nilcaso.fuente
try?condecode, es mejor usartrycondecodeIfPresenten 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) ?? 00000así que si falla, solo pondrá 0000 pero aún falla.decodeIfPresentestá malAPIporque la clave existe. Usa otrodo - catchbloque. DecodificarString, si ocurre un error, decodificarIntUna 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
friendnamesArrayes 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
FailableCodableArraya:fuente
En su lugar, también puede hacer esto:
y luego adentro mientras lo obtengo:
fuente
Se me ocurre esto
KeyedDecodingContainer.safelyDecodeArrayque proporciona una interfaz simple:El bucle potencialmente infinito
while !container.isAtEndes 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