134 votes

Comment utiliser des clés personnalisées avec le protocole Decodable de Swift 4?

Swift 4 introduit la prise en charge native de codage et décodage JSON via l' Decodable protocole. Comment puis-je personnaliser les touches pour cela?

E. g., dire que j'ai une struct

struct Address:Codable {
    var street:String
    var zip:String
    var city:String
    var state:String
}

Je peux coder cette JSON.

let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")

if let encoded = try? encoder.encode(address) {
    if let json = String(data: encoded, encoding: .utf8) {
        // Print JSON String
        print(json)

        // JSON string is 
           { "state":"California", 
             "street":"Apple Bay Street", 
             "zip":"94608", 
             "city":"Emeryville" 
           }
    }
}

Je peux coder ce retour à un objet.

    let newAddress: Address = try decoder.decode(Address.self, from: encoded)

Mais Si j'avais un objet json qui a été

{ 
   "state":"California", 
   "street":"Apple Bay Street", 
   "zip_code":"94608", 
   "city":"Emeryville" 
}

Comment pourrais-je dire le décodeur sur Address que zip_code cartes d' zip? Je crois que vous utilisez le nouveau CodingKey protocole, mais je ne peux pas comprendre comment les utiliser.

336voto

Hamish Points 42073

Manuellement, la personnalisation des clés de codage

Dans votre exemple, vous avez une auto-généré, de la conformité à Codable que l'ensemble de vos propriétés également respecter Codable. Cette conformité crée automatiquement un type de clé qui correspond tout simplement à la propriété des noms – qui est ensuite utilisé pour encoder pour/décodage à partir d'une clé unique conteneur.

Cependant, l'un vraiment agréable de cette auto-généré à la conformité de l'est que si vous définissez une imbriqués enum dans votre type appelé "CodingKeys" (ou utiliser un typealias avec ce nom), qui est conforme à l' CodingKey protocole Swift utilise automatiquement ce que le type de clé. Ceci permet donc de facilement personnaliser les clés de vos propriétés sont codés/décodés avec.

Donc, ce que cela signifie est que vous pouvez simplement dire:

struct Address : Codable {

    var street: String
    var zip: String
    var city: String
    var state: String

    private enum CodingKeys : String, CodingKey {
        case street, zip = "zip_code", city, state
    }
}

L'énumération des noms de cas besoin de faire correspondre les noms de propriété, et les valeurs brutes de ces cas besoin de faire correspondre les touches que vous êtes de l'encodage/décodage à partir de (sauf si spécifié autrement, les premières valeurs de String énumération sera le même que le cas des noms). Par conséquent, l' zip de la propriété sera désormais codés/décodés à l'aide de la touche "zip_code".

Les règles exactes pour l'auto-générés Encodable/Decodable de conformité sont détaillées en fonction de l'évolution de la proposition (l'emphase est mienne):

En plus automatiques CodingKey exigence de synthèse pour enums, Encodable & Decodable exigences peuvent être automatiquement synthétisé pour certains types ainsi:

  1. Types conformes à l' Encodable dont les propriétés sont tous Encodable obtenir généré automatiquement String-backed CodingKey enum cartographie propriétés à des noms de cas. De même, pour l' Decodable , dont les propriétés sont tous Decodable

  2. Types de tomber dans (1) - et de types de fournir manuellement une CodingKey enum (nommé en CodingKeys, directement ou par l'intermédiaire d'un typealias) dont cas de la carte 1-à-1 Encodable/Decodable propriétés par nom - get automatique de la synthèse de l' init(from:) et encode(to:) comme il convient, l'utilisation de ces propriétés et des touches de

  3. Les Types qui tombent dans ni (1) ni (2) devra fournir une clé personnalisée de type si nécessaire et de fournir leurs propres init(from:)et encode(to:), selon le cas

Exemple d'encodage:

import Foundation

let address = Address(street: "Apple Bay Street", zip: "94608",
                      city: "Emeryville", state: "California")

do {
    let encoded = try JSONEncoder().encode(address)
    print(String(decoding: encoded, as: UTF8.self))
} catch {
    print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}

Exemple de décodage:

// using the """ multi-line string literal here, as introduced in SE-0168,
// to avoid escaping the quotation marks
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""

do {
    let decoded = try JSONDecoder().decode(Address.self, from: Data(jsonString.utf8))
    print(decoded)
} catch {
    print(error)
}

// Address(street: "Apple Bay Street", zip: "94608",
// city: "Emeryville", state: "California")

Automatique snake_case JSON clés pour camelCase noms de propriété

Dans Swift 4.1, si vous renommez votre zip de la propriété d' zipCode,, vous pouvez profiter de la clé de codage/décodage des stratégies JSONEncoder et JSONDecoder afin de convertir automatiquement les clés de codage entre camelCase et snake_case.

Exemple d'encodage:

import Foundation

struct Address : Codable {
  var street: String
  var zipCode: String
  var city: String
  var state: String
}

let address = Address(street: "Apple Bay Street", zipCode: "94608",
                      city: "Emeryville", state: "California")

do {
  let encoder = JSONEncoder()
  

Exemple de décodage:

encoder.keyEncodingStrategy = .convertToSnakeCase

Une chose importante à noter à propos de cette stratégie, cependant, est qu'il ne sera pas en mesure d'aller-retour de certains noms de propriété avec les acronymes ou sigles qui, selon l' API Swift lignes directrices de conception, doit être uniformément cas supérieure ou inférieure (selon le poste).

Par exemple, une propriété nommée let encoded = try encoder.encode(address) print(String(decoding: encoded, as: UTF8.self)) } catch { print(error) } //{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} sera codé avec la clé let jsonString = """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} """ do { let decoder = JSONDecoder() , mais sur le décodage, ce sera transformée en decoder.keyDecodingStrategy = .convertFromSnakeCase.

Pour résoudre ce problème, vous devez spécifier manuellement la clé de codage pour cette propriété à la chaîne que le décodeur s'attend, e.g let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8)) print(decoded) } catch { print(error) } // Address(street: "Apple Bay Street", zipCode: "94608", // city: "Emeryville", state: "California") dans ce cas (qui va encore être transformé someURL par le codeur):

some_url

(Ce qui n'est pas strictement répondre à une question particulière, mais compte tenu de la canoniques de la nature de ce Q&A, je pense que c'est utile notamment)

Personnalisé automatique JSON mappage de touches

Dans Swift 4.1, vous pouvez profiter de la coutume de la clé de codage/décodage des stratégies someUrl et someUrl, vous permettant de fournir une fonction personnalisée à la carte des clés de codage.

La fonction que vous fournissez prend un some_url, ce qui représente le codage chemin d'accès pour le point en cours de codage/décodage (dans la plupart des cas, vous aurez seulement besoin de considérer le dernier élément, qui est, à la clé). La fonction retourne un struct S : Codable { private enum CodingKeys : String, CodingKey { case someURL = "someUrl", someOtherProperty } var someURL: String var someOtherProperty: String } qui va remplacer la dernière touche à ce tableau.

Par exemple, JSONEncoder JSON clés pour JSONDecoder noms de propriété:

[CodingKey]

CodingKey

UpperCamelCase

Vous pouvez coder avec l' lowerCamelCase clés de la stratégie:

import Foundation

// wrapper to allow us to substitute our mapped string keys.
struct AnyCodingKey : CodingKey {

  var stringValue: String
  var intValue: Int?

  init(_ base: CodingKey) {
    self.init(stringValue: base.stringValue, intValue: base.intValue)
  }

  init(stringValue: String) {
    self.stringValue = stringValue
  }

  init(intValue: Int) {
    self.stringValue = "\(intValue)"
    self.intValue = intValue
  }

  init(stringValue: String, intValue: Int?) {
    self.stringValue = stringValue
    self.intValue = intValue
  }
}

et décoder avec l' extension JSONEncoder.KeyEncodingStrategy { static var convertToUpperCamelCase: JSONEncoder.KeyEncodingStrategy { return .custom { codingKeys in var key = AnyCodingKey(codingKeys.last!) // uppercase first letter if let firstChar = key.stringValue.first { let i = key.stringValue.startIndex key.stringValue.replaceSubrange( i ... i, with: String(firstChar).uppercased() ) } return key } } } clés de la stratégie:

extension JSONDecoder.KeyDecodingStrategy {

  static var convertFromUpperCamelCase: JSONDecoder.KeyDecodingStrategy {
    return .custom { codingKeys in

      var key = AnyCodingKey(codingKeys.last!)

      // lowercase first letter
      if let firstChar = key.stringValue.first {
        let i = key.stringValue.startIndex
        key.stringValue.replaceSubrange(
          i ... i, with: String(firstChar).lowercased()
        )
      }
      return key
    }
  }
}

29voto

Imanou Petit Points 43068

Avec Swift 4.2, selon vos besoins, vous pouvez utiliser l'une des 3 stratégies suivantes dans le but de rendre vos objets de modèle personnalisé de la propriété des noms correspondent à vos JSON clés.


#1. En utilisant des clés de codage

Lorsque vous déclarez une structure conforme à l' Codable (Decodable et Encodable protocoles) à la suite de la mise en œuvre...

struct Address: Codable {
    var street: String
    var zip: String
    var city: String
    var state: String        
}

... le compilateur génère automatiquement un imbriquée enum qui est conforme à l' CodingKey protocole pour vous.

struct Address: Codable {
    var street: String
    var zip: String
    var city: String
    var state: String

    // compiler generated
    private enum CodingKeys: String, CodingKey {
        case street
        case zip
        case city
        case state
    }
}

Par conséquent, si les clés utilisés dans votre format de données sérialisées ne correspondent pas à la propriété des noms de votre type de données, vous pouvez manuellement mettre en œuvre cette enum et set approprié rawValue pour le cas.

L'exemple suivant montre comment faire:

import Foundation

struct Address: Codable {
    var street: String
    var zip: String
    var city: String
    var state: String

    private enum CodingKeys: String, CodingKey {
        case street
        case zip = "zip_code"
        case city
        case state
    }
}

Coder (remplacer zip de la propriété avec "zip_code" JSON clé):

let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")

let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

/*
 prints:
 {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
 */

Décoder (en remplaçant "zip_code" JSON clé avec zip de la propriété):

let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""

let decoder = JSONDecoder()
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
    print(address)
}

/*
 prints:
 Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
 */

#2. À l'aide de serpent cas de chameau cas de la clé des stratégies de codage

Si votre JSON a de serpent enfermé les clés et que vous souhaitez les convertir en camelcase les propriétés de votre objet de modèle, vous pouvez définir votre JSONEncoders' keyEncodingStrategy et JSONDecoders' keyDecodingStrategy propriétés d' .convertToSnakeCase.

L'exemple suivant montre comment faire:

import Foundation

struct Address: Codable {
    var street: String
    var zipCode: String
    var cityName: String
    var state: String
}

Coder (conversion camelcase propriétés en serpent tubé JSON clés):

let address = Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

/*
 prints:
 {"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"}
 */

Décoder (conversion de serpent tubé JSON clés en camelcase propriétés):

let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"}
"""

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
    print(address)
}

/*
 prints:
 Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")
 */

#3. À l'aide d'une clé personnalisée stratégies de codage

Si nécessaire, JSONEncoder et JSONDecoder vous permettent de définir une stratégie personnalisée à la carte des clés de codage à l'aide de JSONEncoder.KeyEncodingStrategy.custom(_:) et JSONDecoder.KeyDecodingStrategy.custom(_:).

L'exemple suivant montre comment les mettre en œuvre:

import Foundation

struct Address: Codable {
    var street: String
    var zip: String
    var city: String
    var state: String
}

struct AnyKey: CodingKey {
    var stringValue: String
    var intValue: Int?

    init?(stringValue: String) {
        self.stringValue = stringValue
    }

    init?(intValue: Int) {
        self.stringValue = String(intValue)
        self.intValue = intValue
    }
}

Coder (conversion en lowercase premières propriétés de la lettre en en majuscule la première lettre JSON clés):

let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .custom({ (keys) -> CodingKey in
    let lastKey = keys.last!
    guard lastKey.intValue == nil else { return lastKey }
    let stringValue = lastKey.stringValue.prefix(1).uppercased() + lastKey.stringValue.dropFirst()
    return AnyKey(stringValue: stringValue)!
})

if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
    print(jsonString)
}

/*
 prints:
 {"Zip":"94608","Street":"Apple Bay Street","City":"Emeryville","State":"California"}
 */

Décoder (conversion en majuscule la première lettre JSON clés en lowercase premières propriétés de la lettre):

let jsonString = """
{"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"}
"""

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in
    let lastKey = keys.last!
    guard lastKey.intValue == nil else { return lastKey }
    let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst()
    return AnyKey(stringValue: stringValue)!
})

if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
    print(address)
}

/*
 prints:
 Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
 */

Sources:

3voto

Tushar Points 21

Ce que j'ai fait c'est de créer de structure propre, tout comme ce que vous obtenez à partir du JSON à l'égard de ses types de données.

Juste comme ça:

struct Track {
let id : Int
let contributingArtistNames:String
let name : String
let albumName :String
let copyrightP:String
let copyrightC:String
let playlistCount:Int
let trackPopularity:Int
let playlistFollowerCount:Int
let artistFollowerCount : Int
let label : String
}

Après cela, vous devez créer une extension à de même struct étendant decodable et de la enum de la même structure avec CodingKey , puis vous devez initialiser le décodeur à l'aide de cette enum, avec ses clés et types de données (Clés viendra de l'enum et les types de données sera de venir ou de dire référencé à partir de la structure elle-même)

extension Track: Decodable {

    enum TrackCodingKeys: String, CodingKey {
        case id = "id"
        case contributingArtistNames = "primaryArtistsNames"
        case spotifyId = "spotifyId"
        case name = "name"
        case albumName = "albumName"
        case albumImageUrl = "albumImageUrl"
        case copyrightP = "copyrightP"
        case copyrightC = "copyrightC"
        case playlistCount = "playlistCount"
        case trackPopularity = "trackPopularity"
        case playlistFollowerCount = "playlistFollowerCount"
        case artistFollowerCount = "artistFollowers"
        case label = "label"
    }
    init(from decoder: Decoder) throws {
        let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
        if trackContainer.contains(.id){
            id = try trackContainer.decode(Int.self, forKey: .id)
        }else{
            id = 0
        }
        if trackContainer.contains(.contributingArtistNames){
            contributingArtistNames = try trackContainer.decode(String.self, forKey: .contributingArtistNames)
        }else{
            contributingArtistNames = ""
        }
        if trackContainer.contains(.spotifyId){
            spotifyId = try trackContainer.decode(String.self, forKey: .spotifyId)
        }else{
            spotifyId = ""
        }
        if trackContainer.contains(.name){
            name = try trackContainer.decode(String.self, forKey: .name)
        }else{
            name = ""
        }
        if trackContainer.contains(.albumName){
            albumName = try trackContainer.decode(String.self, forKey: .albumName)
        }else{
            albumName = ""
        }
        if trackContainer.contains(.albumImageUrl){
            albumImageUrl = try trackContainer.decode(String.self, forKey: .albumImageUrl)
        }else{
            albumImageUrl = ""
        }
        if trackContainer.contains(.copyrightP){
            copyrightP = try trackContainer.decode(String.self, forKey: .copyrightP)
        }else{
            copyrightP = ""
        }
        if trackContainer.contains(.copyrightC){
                copyrightC = try trackContainer.decode(String.self, forKey: .copyrightC)
        }else{
            copyrightC = ""
        }
        if trackContainer.contains(.playlistCount){
            playlistCount = try trackContainer.decode(Int.self, forKey: .playlistCount)
        }else{
            playlistCount = 0
        }

        if trackContainer.contains(.trackPopularity){
            trackPopularity = try trackContainer.decode(Int.self, forKey: .trackPopularity)
        }else{
            trackPopularity = 0
        }
        if trackContainer.contains(.playlistFollowerCount){
            playlistFollowerCount = try trackContainer.decode(Int.self, forKey: .playlistFollowerCount)
        }else{
            playlistFollowerCount = 0
        }

        if trackContainer.contains(.artistFollowerCount){
            artistFollowerCount = try trackContainer.decode(Int.self, forKey: .artistFollowerCount)
        }else{
            artistFollowerCount = 0
        }
        if trackContainer.contains(.label){
            label = try trackContainer.decode(String.self, forKey: .label)
        }else{
            label = ""
        }
    }
}

Vous avez besoin de changer ici chaque clé et les types de données en fonction de vos besoins et de l'utiliser avec le décodeur.

0voto

Renjish C Points 16

En utilisant CodingKey, vous pouvez utiliser des clés personnalisées dans un protocole codable ou décodable.

 struct person: Codable {
    var name: String
    var age: Int
    var street: String
    var state: String

    private enum CodingKeys: String, CodingKey {
        case name
        case age
        case street = "Street_name"
        case state
    } }
 

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