Ręczne dostosowywanie kluczy kodowania
W swoim przykładzie otrzymujesz automatycznie wygenerowaną zgodność, z którą Codable
są 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 enum
w swoim typie o nazwie " CodingKeys
" (lub użyjesz a typealias
z tą nazwą), który jest zgodny z CodingKey
protokoł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 String
wyliczenia będą takie same jak nazwy przypadków ). Dlatego zip
właściwość zostanie teraz zakodowana / zdekodowana przy użyciu klucza "zip_code"
.
Dokładne zasady generowania automatycznego Encodable
/ Decodable
zgodności są szczegółowo opisane w propozycji ewolucji (moje podkreślenie):
Oprócz automatycznej CodingKey
syntezy wymagań dla
enums
, wymagania Encodable
& Decodable
mogą być również automatycznie syntetyzowane dla niektórych typów:
Typy zgodne z Encodable
wszystkimi właściwościami są Encodable
generowane automatycznieString
-backed CodingKey
właściwości mapowania enum do nazw sprawy. Podobnie dla Decodable
typó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 camelCase
nazw właściwości
W Swift 4.1, jeśli zmienisz nazwę swojej zip
właściwości na zipCode
, możesz skorzystać ze strategii kodowania / dekodowania klucza na JSONEncoder
iJSONDecoder
w celu automatycznej konwersji kluczy kodowania między camelCase
a 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 someURL
zostanie 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. someUrl
W tym przypadku (który nadal będzie przekształcany some_url
przez 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 JSONEncoder
i 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 UpperCamelCase
klucze JSON dla lowerCamelCase
nazw 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ą .convertToUpperCamelCase
strategii 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 .convertFromUpperCamelCase
kluczową 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")
CodingKeys
wyliczeniem; czy mogę wymienić tylko jeden klucz, który zmieniam?