Ręczne dostosowywanie kluczy kodowania
W swoim przykładzie otrzymujesz automatycznie wygenerowaną zgodność, z którą Codablesą zgodne również wszystkie twoje właściwości Codable. Ta zgodność automatycznie tworzy typ klucza, który po prostu odpowiada nazwom właściwości - który jest następnie używany do kodowania do / dekodowania z kontenera z pojedynczym kluczem.
Jednak jeden naprawdę fajną cechą tej automatycznie generowanej zgodności jest to, że jeśli zdefiniujesz zagnieżdżony enumw swoim typie o nazwie " CodingKeys" (lub użyjesz a typealiasz tą nazwą), który jest zgodny z CodingKeyprotokołem - Swift automatycznie użyje tego jako typu klucza. Dzięki temu możesz łatwo dostosować klucze, za pomocą których twoje właściwości są kodowane / dekodowane.
Oznacza to, że możesz po prostu powiedzieć:
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
}
}
Nazwy przypadków wyliczenia muszą pasować do nazw właściwości, a surowe wartości tych przypadków muszą być zgodne z kluczami, do których kodujesz / dekodujesz (o ile nie określono inaczej, surowe wartości Stringwyliczenia będą takie same jak nazwy przypadków ). Dlatego zipwłaściwość zostanie teraz zakodowana / zdekodowana przy użyciu klucza "zip_code".
Dokładne zasady generowania automatycznego Encodable / Decodablezgodności są szczegółowo opisane w propozycji ewolucji (moje podkreślenie):
Oprócz automatycznej CodingKeysyntezy wymagań dla
enums , wymagania Encodable& Decodablemogą być również automatycznie syntetyzowane dla niektórych typów:
Typy zgodne z Encodablewszystkimi właściwościami są Encodablegenerowane automatycznieString -backed CodingKeywłaściwości mapowania enum do nazw sprawy. Podobnie dla Decodabletypów, których wszystkie właściwości sąDecodable
Typy należące do (1) - i typy, które ręcznie zapewniają CodingKey enum(nazwane CodingKeys, bezpośrednio lub przez a typealias), których przypadki mapują 1-do-1 na Encodable/Decodable właściwości według nazwy - uzyskują automatyczną syntezę init(from:)i, encode(to:)jeśli to konieczne, przy użyciu tych właściwości i kluczy
Typy, które nie należą do ani (1), ani (2) będą musiały w razie potrzeby podać niestandardowy typ klucza i zapewnić własne init(from:)i
encode(to:) , w razie potrzeby
Przykładowe kodowanie:
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"}
Przykładowe dekodowanie:
// 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")
Automatyczny snake_case klucze JSON dla camelCasenazw właściwości
W Swift 4.1, jeśli zmienisz nazwę swojej zipwłaściwości na zipCode, możesz skorzystać ze strategii kodowania / dekodowania klucza na JSONEncoderiJSONDecoder w celu automatycznej konwersji kluczy kodowania między camelCasea snake_case.
Przykładowe kodowanie:
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"}
Przykładowe dekodowanie:
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")
Jedną ważną rzeczą, na którą należy zwrócić uwagę na temat tej strategii, jest to, że nie będzie ona w stanie przelać w obie strony niektórych nazw właściwości za pomocą akronimów lub inicjałów, które zgodnie z wytycznymi projektowymi Swift API powinny być jednolicie duże lub małe (w zależności od pozycji ).
Na przykład właściwość o nazwie someURLzostanie zakodowana za pomocą klucza some_url, ale podczas dekodowania zostanie przekształcona na someUrl.
Aby to naprawić, musisz ręcznie określić klucz kodowania dla tej właściwości jako ciąg, którego dekoder oczekuje, np. someUrlW tym przypadku (który nadal będzie przekształcany some_urlprzez koder):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(To nie jest dokładną odpowiedzią na twoje konkretne pytanie, ale biorąc pod uwagę kanoniczny charakter tych pytań i odpowiedzi, uważam, że warto je uwzględnić)
Niestandardowe automatyczne mapowanie kluczy JSON
W Swift 4.1 możesz skorzystać z niestandardowych strategii kodowania / dekodowania kluczy włączonych JSONEncoderi JSONDecoder, co pozwala na zapewnienie niestandardowej funkcji mapowania kluczy kodowania.
Podana funkcja przyjmuje znak a [CodingKey], który reprezentuje ścieżkę kodowania dla bieżącego punktu w kodowaniu / dekodowaniu (w większości przypadków wystarczy wziąć pod uwagę tylko ostatni element, czyli bieżący klucz). Funkcja zwraca wartość CodingKey, która zastąpi ostatni klucz w tej tablicy.
Na przykład UpperCamelCaseklucze JSON dla lowerCamelCasenazw właściwości:
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
}
}
}
Możesz teraz kodować za pomocą .convertToUpperCamelCasestrategii klucza:
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"}
i dekoduj z .convertFromUpperCamelCasekluczową strategią:
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")
CodingKeyswyliczeniem; czy mogę wymienić tylko jeden klucz, który zmieniam?