Swift 4 a ajouté le nouveau Codable
protocole. Lorsque je l'utilise, JSONDecoder
il semble que toutes les propriétés non facultatives de ma Codable
classe aient des clés dans le JSON ou cela génère une erreur.
Rendre chaque propriété de ma classe facultative semble être un problème inutile car ce que je veux vraiment, c'est utiliser la valeur dans le json ou une valeur par défaut. (Je ne veux pas que la propriété soit nulle.)
Y a-t-il un moyen de faire cela?
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
Réponses:
L'approche que je préfère utilise ce qu'on appelle les DTO - objet de transfert de données. Il s'agit d'une structure conforme à Codable et représentant l'objet souhaité.
Ensuite, vous lancez simplement l'objet que vous souhaitez utiliser dans l'application avec ce DTO.
Cette approche est également bonne car vous pouvez renommer et modifier l'objet final comme vous le souhaitez. Il est clair et nécessite moins de code que le décodage manuel. De plus, avec cette approche, vous pouvez séparer la couche réseau des autres applications.
la source
Vous pouvez implémenter la
init(from decoder: Decoder)
méthode dans votre type au lieu d'utiliser l'implémentation par défaut:Vous pouvez également créer
name
une propriété constante (si vous le souhaitez):ou
À propos de votre commentaire: avec une extension personnalisée
vous pouvez implémenter la méthode init comme
mais ce n'est pas beaucoup plus court que
la source
CodingKeys
énumération générée automatiquement (vous pouvez donc supprimer la définition personnalisée) :)ObjectMapper
gère cela très bien.Decodable
et fournissez également votre propre implémentation deinit(from:)
? Dans ce cas, le compilateur suppose que vous souhaitez gérer vous-même le décodage manuellement et ne synthétise donc pas d'CodingKeys
énumération pour vous. Comme vous le dites, se conformer à laCodable
place fonctionne parce que maintenant le compilateur synthétiseencode(to:)
pour vous et synthétise donc égalementCodingKeys
. Si vous fournissez également votre propre implémentation deencode(to:)
,CodingKeys
ne sera plus synthétisé.Une solution serait d'utiliser une propriété calculée qui prend par défaut la valeur souhaitée si la clé JSON n'est pas trouvée. Cela ajoute un peu de verbosité supplémentaire car vous devrez déclarer une autre propriété et nécessitera l'ajout de l'
CodingKeys
énumération (si ce n'est déjà fait). L'avantage est que vous n'avez pas besoin d'écrire de code de décodage / encodage personnalisé.Par exemple:
la source
Vous pouvez mettre en œuvre.
la source
Si vous ne souhaitez pas implémenter vos méthodes d'encodage et de décodage, il existe une solution quelque peu sale autour des valeurs par défaut.
Vous pouvez déclarer votre nouveau champ comme optionnel implicitement déballé et vérifier s'il est nul après le décodage et définir une valeur par défaut.
J'ai testé cela uniquement avec PropertyListEncoder, mais je pense que JSONDecoder fonctionne de la même manière.
la source
Je suis tombé sur cette question en cherchant exactement la même chose. Les réponses que j'ai trouvées n'étaient pas très satisfaisantes même si j'avais peur que les solutions ici ne soient la seule option.
Dans mon cas, la création d'un décodeur personnalisé nécessiterait une tonne de passe-partout qui serait difficile à entretenir, alors j'ai continué à chercher d'autres réponses.
Je suis tombé sur cet article qui montre un moyen intéressant de surmonter cela dans des cas simples en utilisant un
@propertyWrapper
. La chose la plus importante pour moi, c'était qu'il était réutilisable et nécessitait une refactorisation minimale du code existant.L'article suppose un cas où vous voudriez qu'une propriété booléenne manquante soit par défaut false sans échouer, mais montre également d'autres variantes différentes. Vous pouvez le lire plus en détail mais je vais vous montrer ce que j'ai fait pour mon cas d'utilisation.
Dans mon cas, j'avais un
array
que je voulais initialiser comme vide si la clé manquait.Donc, j'ai déclaré les
@propertyWrapper
extensions suivantes et supplémentaires:L'avantage de cette méthode est que vous pouvez facilement résoudre le problème dans le code existant en ajoutant simplement le
@propertyWrapper
à la propriété. Dans mon cas:J'espère que cela aidera quelqu'un à faire face au même problème.
METTRE À JOUR:
Après avoir publié cette réponse tout en continuant à examiner la question, j'ai trouvé cet autre article, mais surtout la bibliothèque respective qui contient des éléments communs faciles à utiliser
@propertyWrapper
pour ce type de cas:https://github.com/marksands/BetterCodable
la source
Si vous pensez qu'écrire votre propre version de
init(from decoder: Decoder)
est écrasant, je vous conseillerais d'implémenter une méthode qui vérifiera l'entrée avant de l'envoyer au décodeur. De cette façon, vous aurez un endroit où vous pourrez vérifier l'absence de champs et définir vos propres valeurs par défaut.Par exemple:
Et pour initier un objet depuis json, au lieu de:
Init ressemblera à ceci:
Dans cette situation particulière, je préfère traiter les options mais si vous avez un avis différent, vous pouvez rendre votre méthode customDecode (:) jetable
la source