Come utilizzare i tasti personalizzati con il protocollo Decodable di Swift 4?

Swift 4 ha introdotto il supporto per la codifica e la decodifica JSON nativa tramite il protocollo Decodable . Come uso le chiavi personalizzate per questo?

Ad esempio, dire che ho una struttura

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

Posso codificare questo a 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" } } } 

Posso ricondurlo a un object.

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

Ma se avessi un object JSON che era

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

Come dovrei dire al decoder su Address che zip_code mappa per zip ? Credo che tu usi il nuovo protocollo CodingKey , ma non riesco a capire come usarlo.

Personalizzazione manuale delle chiavi di codifica

Nel tuo esempio, stai ottenendo una conformità generata automaticamente a Codable poiché tutte le tue proprietà sono conformi anche a Codable . Questa conformità crea automaticamente un tipo di chiave che corrisponde semplicemente ai nomi di proprietà, che viene quindi utilizzato per codificare / decodificare da un singolo contenitore con chiave.

Tuttavia, una caratteristica veramente chiara di questa conformità generata automaticamente è che se si definisce un CodingKeys nidificato nel proprio tipo chiamato ” CodingKeys ” (o si utilizza un typealias con questo nome) conforms al protocollo CodingKey , Swift lo utilizzerà automaticamente come chiave genere. Ciò consente quindi di personalizzare facilmente i tasti con cui le proprietà vengono codificate / decodificate.

Quindi, questo significa che puoi semplicemente 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 } } 

I nomi dei casi enum devono corrispondere ai nomi delle proprietà e i valori non elaborati di questi casi devono corrispondere alle chiavi da codificare / decodificare (a meno che non sia specificato diversamente, i valori non String un’enumerazione String saranno uguali a quelli del caso nomi). Pertanto, la proprietà zip verrà ora codificata / decodificata utilizzando la chiave "zip_code" .

Le regole esatte per la conformità Encodable / Decodable generata Encodable sono dettagliate dalla proposta di evoluzione (enfasi mia):

Oltre alla sintesi automatica CodingKey requisiti CodingKey per enums , i requisiti Encodable & Decodable possono essere sintetizzati automaticamente anche per alcuni tipi:

  1. I tipi conformi a Encodable cui proprietà sono tutte Encodable ottengono automaticamente le proprietà di mapping enum CodingKey StringCodingKey ai nomi dei casi. Allo stesso modo per i tipi Decodable cui proprietà sono tutte Decodable

  2. Tipi che rientrano in (1) – e tipi che forniscono manualmente un CodingKey (chiamato CodingKeys , direttamente o tramite typealias ) i cui casi mappano da 1 a 1 a proprietà Encodable / Decodable per nome – ottengono la sintesi automatica di init(from:) e encode(to:) seconda dei casi, utilizzando tali proprietà e chiavi

  3. I tipi che non rientrano né in (1) né in (2) dovranno fornire un tipo di chiave personalizzata se necessario e fornire il proprio init(from:) e encode(to:) , come appropriato

Esempio di codifica:

 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"} 

Decodifica di esempio:

 // 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") 

snake_case JSON automatiche snake_case per i nomi delle proprietà di camelCase

In Swift 4.1 (disponibile in Xcode 9.3 beta), se si rinomina la proprietà zip in zipCode , è ansible sfruttare le strategie chiave di codifica / decodifica su JSONEncoder e JSONDecoder per convertire automaticamente le chiavi di codifica tra camelCase e snake_case .

Esempio di codifica:

 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() encoder.keyEncodingStrategy = .convertToSnakeCase 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"} 

Decodifica di esempio:

 let jsonString = """ {"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"} """ do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase 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") 

Una cosa importante da notare su questa strategia è che non sarà in grado di aggirare alcuni nomi di proprietà con acronimi o inizialismi che, secondo le linee guida di progettazione dell’API Swift , dovrebbero essere uniformsmente maiuscole o minuscole (a seconda della posizione ).

Ad esempio, una proprietà denominata someURL sarà codificata con la chiave some_url , ma nella decodifica, questa verrà trasformata in someUrl .

Per risolvere questo problema, dovrai specificare manualmente la chiave di codifica per quella proprietà come stringa che il decodificatore si aspetta, ad es. someUrl in questo caso (che verrà comunque trasformato in some_url dal codificatore):

 struct S : Codable { private enum CodingKeys : String, CodingKey { case someURL = "someUrl", someOtherProperty } var someURL: String var someOtherProperty: String } 

(Questo non risponde rigorosamente alla tua domanda specifica, ma data la natura canonica di questo Q & A, ritengo che valga la pena includere)

Mappatura dei tasti JSON automatica personalizzata

In Swift 4.1 (disponibile in Xcode 9.3 beta), è ansible sfruttare le strategie di codifica / decodifica dei tasti personalizzati su JSONEncoder e JSONDecoder , consentendo di fornire una funzione personalizzata per mappare le chiavi di codifica.

La funzione fornita richiede [CodingKey] , che rappresenta il percorso di codifica per il punto corrente in codifica / decodifica (nella maggior parte dei casi, dovrai solo prendere in considerazione l’ultimo elemento, ovvero la chiave corrente). La funzione restituisce un CodingKey che sostituirà l’ultima chiave in questo array.

Ad esempio, le chiavi JSON di lowerCamelCase per i nomi di proprietà lowerCamelCase :

 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 } } 

 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 } } } 

 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 } } } 

Ora puoi codificare con la strategia chiave .convertToUpperCamelCase :

 let address = Address(street: "Apple Bay Street", zipCode: "94608", city: "Emeryville", state: "California") do { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToUpperCamelCase let encoded = try encoder.encode(address) print(String(decoding: encoded, as: UTF8.self)) } catch { print(error) } //{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"} 

e decodificare con la strategia chiave .convertFromUpperCamelCase :

 let jsonString = """ {"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"} """ do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromUpperCamelCase 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") 

Quando dichiari una struttura conforms a Codable (protocolli Decodable e Encodable ) con la seguente implementazione …

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

… il compilatore genera automaticamente un CodingKey nidificato conforms al protocollo CodingKey per te.

 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 } } 

Pertanto, se le chiavi utilizzate nel formato dati serializzato non corrispondono ai nomi di proprietà del tipo di dati, è necessario implementare manualmente questo enum e impostare il valore rawValue appropriato per i casi richiesti:

 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 } } 

Uso # 1: codifica un’istanza di Address in una stringa JSON

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

Uso n. 2: decodifica una stringa JSON in un’istanza di Address

 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") */ 

fonti:

  • Codifica e decodifica dei tipi personalizzati
  • Sessione WWDC 2017 212 “Novità in Fondazione”