web-dev-qa-db-fra.com

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

Swift 4 a introduit la prise en charge du codage et du décodage JSON natif via le protocole Decodable . Comment utiliser les clés personnalisées pour cela?

Par exemple, disons que j'ai un struct

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

Je peux encoder ceci en 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 ré-encoder ceci en un objet.

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

Mais si j’avais un objet json qui était 

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

Comment pourrais-je dire au décodeur sur Address que Zip_code est mappé sur Zip? Je pense que vous utilisez le nouveau protocole CodingKey, mais je ne vois pas comment l'utiliser.

59
chrismanderson

Avec Swift 4.2, selon vos besoins, vous pouvez utiliser l’une des 3 stratégies suivantes pour que les noms de propriété personnalisés de vos objets de modèle correspondent à vos clés JSON.


#1. Utilisation de clés de codage personnalisées

Lorsque vous déclarez une structure conforme à Codable (protocoles Decodable et Encodable) avec l'implémentation suivante ...

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

... le compilateur génère automatiquement une énumération imbriquée conforme au protocole CodingKey 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ées dans votre format de données sérialisé ne correspondent pas aux noms de propriété de votre type de données, vous pouvez implémenter manuellement cette énumération et définir la variable rawValue appropriée pour les cas requis.

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
    }
}
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"}
 */
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. Utilisation de cas de serpent pour des stratégies de codage clés de cas de chameaux

Si votre JSON a des clés en cascade de serpents et que vous souhaitez les convertir en propriétés camel pour votre objet de modèle, vous pouvez définir vos propriétés JSONEncoder's keyEncodingStrategy et JSONDecoders keyDecodingStrategy en .convertToSnakeCase.

L'exemple suivant montre comment faire:

import Foundation

struct Address: Codable {
    var street: String
    var zipCode: String
    var cityName: String
    var state: String
}
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"}
 */
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. Utilisation de stratégies de codage de clé personnalisées

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

L'exemple suivant montre comment les implémenter:

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
    }
}
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"}
 */
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:

10
Imanou Petit

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
    } }
0
Renjish C

Ce que j'ai fait est de créer sa propre structure, comme ce que vous obtenez du JSON en ce qui concerne 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 la même struct prolongeant decodable et la enum de la même structure avec CodingKey, puis vous devez initialiser le décodeur en utilisant cette énumération avec ses clés et types de données venir ou dire référencé 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 devez modifier ici chaque clé et types de données en fonction de vos besoins et les utiliser avec le décodeur.

0
Tushar