Lors de l'utilisation des protocoles Swift4 et Codable, j'ai eu le problème suivant - il semble qu'il n'y ait aucun moyen d'autoriser JSONDecoder
le saut d'éléments dans un tableau. Par exemple, j'ai le JSON suivant:
[
{
"name": "Banana",
"points": 200,
"description": "A banana grown in Ecuador."
},
{
"name": "Orange"
}
]
Et une structure codable :
struct GroceryProduct: Codable {
var name: String
var points: Int
var description: String?
}
Lors du décodage de ce json
let decoder = JSONDecoder()
let products = try decoder.decode([GroceryProduct].self, from: json)
Le résultat products
est vide. Ce qui est normal, du fait que le deuxième objet dans JSON n'a pas de "points"
clé, alors qu'il points
n'est pas facultatif dans GroceryProduct
struct.
La question est de savoir comment puis-je autoriser JSONDecoder
"ignorer" un objet invalide?
points
simplement être déclaré facultatif?Réponses:
Une option consiste à utiliser un type de wrapper qui tente de décoder une valeur donnée; stockage en
nil
cas d'échec:Nous pouvons ensuite décoder un tableau de ceux-ci, en
GroceryProduct
remplissant l'Base
espace réservé:Nous utilisons ensuite
.compactMap { $0.base }
pour filtrer lesnil
éléments (ceux qui ont généré une erreur de décodage).Cela créera un tableau intermédiaire de
[FailableDecodable<GroceryProduct>]
, ce qui ne devrait pas être un problème; cependant, si vous souhaitez l'éviter, vous pouvez toujours créer un autre type de wrapper qui décode et déballe chaque élément d'un conteneur sans clé:Vous décoderiez alors comme:
la source
var container = try decoder.unkeyedContainer()
init(from:) throws
, donc Swift propagera automatiquement l'erreur à l'appelant (dans ce cas, le décodeur, qui la propagera à l'JSONDecoder.decode(_:from:)
appel).Je créerais un nouveau type
Throwable
, qui peut envelopper tout type conforme àDecodable
:Pour décoder un tableau de
GroceryProduct
(ou tout autreCollection
):où
value
est une propriété calculée introduite dans une extension surThrowable
:J'opterais pour l'utilisation d'un
enum
type wrapper (sur aStruct
) car il peut être utile de garder une trace des erreurs qui sont levées ainsi que de leurs index.Swift 5
Pour Swift 5 Pensez à utiliser par exemple
Result
enum
Pour dérouler la valeur décodée, utilisez la
get()
méthode sur laresult
propriété:la source
init
Le problème est que lors de l'itération sur un conteneur, le container.currentIndex n'est pas incrémenté afin que vous puissiez essayer de décoder à nouveau avec un type différent.
Étant donné que currentIndex est en lecture seule, une solution consiste à l'incrémenter vous-même en décodant avec succès un mannequin. J'ai pris la solution @Hamish et j'ai écrit un wrapper avec un init personnalisé.
Ce problème est un bug actuel de Swift: https://bugs.swift.org/browse/SR-5953
La solution publiée ici est une solution de contournement dans l'un des commentaires. J'aime cette option car j'analyse un tas de modèles de la même manière sur un client réseau, et je voulais que la solution soit locale à l'un des objets. Autrement dit, je veux toujours que les autres soient écartés.
J'explique mieux dans mon github https://github.com/phynet/Lossy-array-decode-swift4
la source
if/else
j'utilise undo/catch
à l'intérieur de lawhile
boucle pour que je puisse enregistrer l'erreurIl existe deux options:
Déclarez tous les membres de la structure comme facultatifs dont les clés peuvent être manquantes
Écrivez un initialiseur personnalisé pour attribuer des valeurs par défaut au
nil
cas.la source
try?
avec,decode
il est préférable d'utilisertry
avecdecodeIfPresent
dans la deuxième option. Nous devons définir la valeur par défaut uniquement s'il n'y a pas de clé, pas en cas d'échec du décodage, comme lorsque la clé existe, mais que le type est incorrect.deviceName = try values.decodeIfPresent(Int.self, forKey: .deviceName) ?? 00000
donc si cela échoue, il mettra simplement 0000 mais cela échoue toujours.decodeIfPresent
c'est fauxAPI
car la clé existe. Utilisez un autredo - catch
bloc. DécoderString
, si une erreur se produit, décoderInt
Une solution rendue possible par Swift 5.1, en utilisant le wrapper de propriété:
Et puis l'utilisation:
Remarque: le wrapper de propriétés ne fonctionnera que si la réponse peut être enveloppée dans une structure (c'est-à-dire: pas un tableau de niveau supérieur). Dans ce cas, vous pouvez toujours l'envelopper manuellement (avec un typealias pour une meilleure lisibilité):
la source
J'ai mis la solution @ sophy-swicz, avec quelques modifications, dans une extension facile à utiliser
Appelez ça comme ça
Pour l'exemple ci-dessus:
la source
Malheureusement, l'API Swift 4 n'a pas d'initialiseur disponible pour
init(from: Decoder)
.Une seule solution que je vois consiste à implémenter un décodage personnalisé, en donnant une valeur par défaut pour les champs facultatifs et un filtre possible avec les données nécessaires:
la source
J'ai eu un problème similaire récemment, mais légèrement différent.
Dans ce cas, si l'un des éléments de
friendnamesArray
est nul, l'objet entier est nul lors du décodage.Et la bonne façon de gérer ce cas de bord est de déclarer le
[String]
tableau de chaînes en[String?]
tant que tableau de chaînes facultatives comme ci-dessous,la source
J'ai amélioré @ Hamish's pour le cas, que vous voulez ce comportement pour tous les tableaux:
la source
La réponse de @ Hamish est excellente. Cependant, vous pouvez réduire
FailableCodableArray
à:la source
Au lieu de cela, vous pouvez également faire comme ceci:
puis en le récupérant:
la source
Je
KeyedDecodingContainer.safelyDecodeArray
propose ceci qui fournit une interface simple:La boucle potentiellement infinie
while !container.isAtEnd
est un problème, et elle est résolue en utilisantEmptyDecodable
.la source
Une tentative beaucoup plus simple: pourquoi ne pas déclarer les points comme facultatifs ou faire en sorte que le tableau contienne des éléments facultatifs
la source