45 votes

Swift 4 Codable; Comment décoder objet avec un seul niveau de la racine de la clé

Je suis à l'aide de la Swift 4 Codable protocole avec les données JSON. Mes données sont formatées telles qu'il existe une unique clé au niveau de la racine avec une valeur d'objet contenant les propriétés dont j'ai besoin, tels que:

{
  "user": {
    "id": 1,
    "username": "jdoe"
  }
}

J'ai un User struct qui peut décoder l' user clé:

struct User: Codable {
  let id: Int
  let username: String
}

Depuis id et username sont des propriétés de l' user, pas au niveau de la racine, j'avais besoin de faire un wrapper type comme suit:

struct UserWrapper: Codable {
  let user: User
}

Je peux ensuite décoder le JSON via l' UserWrapper, et l' User est décodé aussi. Il semble que beaucoup de code redondant puisque je vais avoir besoin d'un adaptateur supplémentaire sur tous les types que j'ai. Est-il un moyen pour éviter ce wrapper motif ou un plus correcte et élégante façon de gérer cette situation?

51voto

Rob Napier Points 92148

Ollie réponse est certainement la meilleure façon d'aller pour ce cas, mais il le fait de repousser de quelques connaissances en l'appelant, qui peut être indésirable. Ce n'est pas très flexible. Je pense toujours que c'est une excellente réponse et exactement ce que vous voulez ici, mais c'est un bel exemple simple pour explorer personnalisé structurels de l'encodage.

Comment pouvons-nous faire ce travail correctement:

let user = try? JSONDecoder().decode(User.self, from: json)

Nous ne pouvons pas utiliser la valeur par défaut de conformité plus. Nous devons construire notre propre décodeur. C'est quelque peu fastidieux, mais pas difficile. Tout d'abord, nous avons besoin de coder la structure en 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 tirant sur 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 voudrais absolument faire Ollie pour ce problème.

Pour beaucoup plus sur ce point, voir l'Encodage et le Décodage des Types Personnalisés.

48voto

Ollie Points 1228

Vous pourriez décoder à l'aide d'un dictionnaire: la combinaison utilisateur puis extraire de 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)

12voto

Paulo Mattos Points 10791

Bien sûr, vous pouvez toujours mettre en place votre propre personnalisé de décodage/encodage - mais pour ce scénario simple votre enveloppe type est une bien meilleure solution de l'OMI ;)

Pour comparaison, la coutume de décodage devrait ressembler à 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 encore pour se conformer à l' Encodable protocole si vous voulez soutenir l'encodage ainsi. Comme je l'ai dit avant, votre simple UserWrapper est beaucoup plus facile ;)

5voto

Edwin Vermeer Points 1904

J'ai créé un helper extension pour Codable qui va faire des choses comme cela plus facile.

voir https://github.com/evermeer/Stuff#codable

Avec cela, vous pouvez créer une instance de l'objet utilisateur comme ceci:

    let v = User(json: json, keyPath: "user")

Vous n'avez pas à changer quoi que ce soit dans votre Utilisateur d'origine struct et vous n'avez pas besoin d'un wrapper.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X