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.
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.
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")
*/
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 JSONDecoder
s 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")
*/
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:
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
} }
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.