J'utilise le protocole Swift 4 Codable
avec des données JSON. Mes données sont formatées de sorte qu'il existe une clé unique au niveau racine avec une valeur d'objet contenant les propriétés I besoin, tel que:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
J'ai une structure User
qui peut décoder la clé user
:
struct User: Codable {
let id: Int
let username: String
}
Puisque id
et username
sont des propriétés de user
, pas au niveau racine, je devais créer un type d’enveloppe comme ceci:
struct UserWrapper: Codable {
let user: User
}
Je peux alors décoder le JSON via le UserWrapper
, et le User
est également décodé. Cela ressemble à beaucoup de code redondant, car il me faut un wrapper supplémentaire pour chaque type que je possède. Existe-t-il un moyen d'éviter ce motif d'emballage ou un moyen plus correct/élégant de gérer cette situation?
Vous pouvez décoder à l'aide d'un dictionnaire: combinaison utilisateur puis extraire l'objet utilisateur. par exemple.
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
La réponse d'Ollie est sans aucun doute la meilleure façon de procéder dans ce cas, mais elle insuffle certaines connaissances à l'appelant, ce qui peut être indésirable. Ce n'est pas très flexible non plus. Je pense toujours que c'est une excellente réponse et exactement ce que vous voulez ici, mais c'est un exemple simple et agréable pour explorer l'encodage structurel personnalisé.
Comment pouvons-nous que cela fonctionne correctement:
let user = try? JSONDecoder().decode(User.self, from: json)
Nous ne pouvons plus utiliser les conformités par défaut. Nous devons construire notre propre décodeur. C'est un peu fastidieux, mais pas difficile. Premièrement, nous devons encoder la structure dans CodingKeys:
struct User {
let id: Int
let username: String
enum CodingKeys: String, CodingKey {
case user // The top level "user" key
}
// The keys inside of the "user" object
enum UserKeys: String, CodingKey {
case id
case username
}
}
Avec cela, nous pouvons décoder User
à la main en extrayant le conteneur imbriqué:
extension User: Decodable {
init(from decoder: Decoder) throws {
// Extract the top-level values ("user")
let values = try decoder.container(keyedBy: CodingKeys.self)
// Extract the user object as a nested container
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
// Extract each property from the nested container
id = try user.decode(Int.self, forKey: .id)
username = try user.decode(String.self, forKey: .username)
}
}
Mais je le ferais absolument pour résoudre ce problème.
Pour plus d'informations à ce sujet, voir Types personnalisés de codage et de décodage .
Bien sûr, vous pouvez toujours implémenter votre propre décodage/encodage personnalisé - mais pour ce scénario simple, votre type d’encapsuleur est une bien meilleure solution IMO;)
À titre de comparaison, le décodage personnalisé ressemblerait à ceci:
struct User {
var id: Int
var username: String
enum CodingKeys: String, CodingKey {
case user
}
enum UserKeys: String, CodingKey {
case id, username
}
}
extension User: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
self.id = try user.decode(Int.self, forKey: .id)
self.username = try user.decode(String.self, forKey: .username)
}
}
et vous devez toujours vous conformer au protocole Encodable
si vous souhaitez également prendre en charge le codage. Comme je l'ai dit précédemment, votre simple UserWrapper
est beaucoup plus facile;)
J'ai créé une extension d'aide pour Codable qui facilitera ce genre de choses.
voir https://github.com/evermeer/Stuff#codable
Avec cela, vous pouvez créer une instance de votre objet utilisateur comme ceci:
let v = User(json: json, keyPath: "user")
Vous n'avez rien à changer dans votre structure utilisateur d'origine et vous n'avez pas besoin d'un wrapper.