Tùy chỉnh thủ công các khóa mã hóa
Trong ví dụ của bạn, bạn đang nhận được một quy tắc được tạo tự động Codable
vì tất cả các thuộc tính của bạn cũng tuân theo Codable
. Sự tuân thủ này tự động tạo ra một loại khóa đơn giản tương ứng với tên thuộc tính - sau đó được sử dụng để mã hóa / giải mã từ một vùng chứa có khóa duy nhất.
Tuy nhiên một thực sự tính năng gọn gàng của sự phù hợp tự động tạo ra này là nếu bạn định nghĩa một lồng enum
trong loại của bạn được gọi là " CodingKeys
" (hoặc sử dụng một typealias
với tên này) mà phù hợp với những CodingKey
giao thức - Swift sẽ tự động sử dụng này làm loại khóa. Do đó, điều này cho phép bạn dễ dàng tùy chỉnh các khóa mà thuộc tính của bạn được mã hóa / giải mã.
Vì vậy, điều này có nghĩa là bạn chỉ có thể nói:
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
}
}
Tên trường hợp enum cần phải khớp với tên thuộc tính và giá trị thô của những trường hợp này cần khớp với các khóa mà bạn đang mã hóa / giải mã từ đó (trừ khi được chỉ định khác, các giá trị thô của kiểu String
liệt kê sẽ giống như tên trường hợp ). Do đó, thuộc zip
tính bây giờ sẽ được mã hóa / giải mã bằng cách sử dụng khóa "zip_code"
.
Các quy tắc chính xác cho sự tuân thủ Encodable
/ được tạo tự động Decodable
được nêu chi tiết bởi đề xuất phát triển (tôi nhấn mạnh):
Ngoài việc CodingKey
tổng hợp yêu cầu tự động cho
enums
, Encodable
& Decodable
các yêu cầu cũng có thể được tổng hợp tự động cho một số loại nhất định:
Các kiểu tuân theo Encodable
thuộc tính của chúng đều Encodable
nhận được thuộc tính ánh xạ enum- String
backed được tạo tự động CodingKey
với tên trường hợp. Tương tự đối với Decodable
các loại có thuộc tính làDecodable
Các loại thuộc (1) - và các loại cung cấp thủ công CodingKey
enum
(được đặt tên CodingKeys
, trực tiếp hoặc thông qua a typealias
) có các trường hợp ánh xạ từ 1 đến 1 đến Encodable
/ Decodable
thuộc tính theo tên - nhận tổng hợp tự động init(from:)
và encode(to:)
khi thích hợp, sử dụng các thuộc tính và khóa đó
Các loại không thuộc (1) và (2) sẽ phải cung cấp loại khóa tùy chỉnh nếu cần và cung cấp loại khóa riêng init(from:)
và
encode(to:)
nếu thích hợp
Mã hóa mẫu:
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"}
Giải mã ví dụ:
// 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")
Các snake_case
khóa JSON tự động cho camelCase
tên thuộc tính
Trong Swift 4.1, nếu bạn đổi tên thuộc tính của mình zip
thành zipCode
, bạn có thể tận dụng các chiến lược mã hóa / giải mã khóa trênJSONEncoder
và JSONDecoder
để tự động chuyển đổi khóa mã hóa giữa camelCase
và snake_case
.
Mã hóa mẫu:
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"}
Giải mã ví dụ:
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")
Tuy nhiên, một điều quan trọng cần lưu ý về chiến lược này là nó sẽ không thể đảo ngược một số tên thuộc tính bằng các từ viết tắt hoặc chữ viết tắt, theo nguyên tắc thiết kế API Swift , phải là chữ hoa hoặc chữ thường (tùy thuộc vào vị trí ).
Ví dụ: một thuộc tính có tên someURL
sẽ được mã hóa bằng khóa some_url
, nhưng khi giải mã, thuộc tính này sẽ được chuyển thành someUrl
.
Để khắc phục điều này, bạn sẽ phải chỉ định thủ công khóa mã hóa cho thuộc tính đó thành chuỗi mà bộ giải mã mong đợi, ví dụ: someUrl
trong trường hợp này (sẽ vẫn được some_url
bộ mã hóa chuyển đổi thành ):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(Điều này không trả lời chính xác câu hỏi cụ thể của bạn, nhưng với bản chất chính tắc của Câu hỏi và Đáp này, tôi cảm thấy nó đáng để bao gồm)
Ánh xạ khóa JSON tự động tùy chỉnh
Trong Swift 4.1, bạn có thể tận dụng các chiến lược mã hóa / giải mã khóa tùy chỉnh trên JSONEncoder
vàJSONDecoder
, cho phép bạn cung cấp một chức năng tùy chỉnh để ánh xạ các khóa mã hóa.
Hàm bạn cung cấp nhận một [CodingKey]
, đại diện cho đường dẫn mã hóa cho điểm hiện tại trong mã hóa / giải mã (trong hầu hết các trường hợp, bạn sẽ chỉ cần xem xét phần tử cuối cùng; nghĩa là khóa hiện tại). Hàm trả về a CodingKey
sẽ thay thế khóa cuối cùng trong mảng này.
Ví dụ: UpperCamelCase
các khóa JSON cho lowerCamelCase
tên thuộc tính:
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
}
}
}
Bây giờ bạn có thể mã hóa bằng .convertToUpperCamelCase
chiến lược chính:
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"}
và giải mã bằng .convertFromUpperCamelCase
chiến lược chính:
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
enum; Tôi có thể liệt kê một khóa mà tôi đang thay đổi không?