web-dev-qa-db-fra.com

Swift 4 Codable; Comment décoder un objet avec une seule clé de niveau racine

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?

31
Joshua Breeden

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)
39
Ollie

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 .

39
Rob Napier

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;)

10
Paulo Mattos

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.

5
Edwin Vermeer