![](/img/trans.png)
[英]How do I convert this JSON into a Swift structure using the Decodable protocol?
[英]How do I use custom keys with Swift 4's Decodable protocol?
Swift 4 通過可解碼協議引入了對原生 JSON 編碼和解碼的Decodable
。 我如何為此使用自定義鍵?
例如,說我有一個結構
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
我可以將其編碼為 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"
}
}
}
我可以將其編碼回一個對象。
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
但是如果我有一個 json 對象
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
我如何告訴解碼器zip_code
映射到zip
Address
? 我相信你使用新的CodingKey
協議,但我不知道如何使用它。
在您的示例中,您將獲得自動生成的Codable
一致性,因為您的所有屬性也符合Codable
。 這種一致性會自動創建一個與屬性名稱簡單對應的密鑰類型——然后使用它來編碼/解碼單個密鑰容器。
但是,這種自動生成的一致性一個真正實用的功能是,如果你定義一個嵌套enum
在您的類型,稱為“ CodingKeys
”(或使用typealias
使用該名稱),一個符合CodingKey
協議-斯威夫特將自動使用這個作為重點類型。 因此,這允許您輕松自定義用於編碼/解碼屬性的密鑰。
所以這意味着你可以說:
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
}
}
枚舉 case 名稱需要匹配屬性名稱,並且這些 case 的原始值需要匹配您編碼/解碼的鍵(除非另有說明,否則String
枚舉的原始值將與 case 相同名稱)。 因此,現在將使用密鑰"zip_code"
對zip
屬性進行編碼/解碼。
自動生成的可Encodable
/可Decodable
一致性的確切規則在進化提案中詳細說明(重點是我的):
除了
enums
自動CodingKey
需求合成之外,Encodable
以為某些類型自動合成可Encodable
和可Decodable
需求:
類型符合
Encodable
,其性質都是Encodable
得到一個自動生成的String
-backedCodingKey
枚舉映射屬性案例名稱。 同樣對於所有屬性都是Decodable
Decodable
類型屬於 (1) 的類型-以及手動提供
CodingKey
enum
(直接命名為CodingKeys
,或通過typealias
)的類型,其情況按名稱將 1 對 1 映射到可Encodable
/ 可Decodable
屬性- 自動合成init(from:)
並適當地encode(to:)
,使用這些屬性和鍵如果需要,既不屬於 (1) 也不屬於 (2) 的類型必須提供自定義鍵類型,並根據需要提供自己的
init(from:)
和encode(to:)
示例編碼:
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"}
示例解碼:
// 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
camelCase
屬性名稱的自動snake_case
JSON鍵在 Swift 4.1 中,如果您將zip
屬性重命名為zipCode
,則可以利用JSONEncoder
和JSONDecoder
上的關鍵編碼/解碼策略,以便在camelCase
和snake_case
之間自動轉換編碼鍵。
示例編碼:
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"}
示例解碼:
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")
然而,關於此策略需要注意的一件重要事情是,它無法使用首字母縮略詞或首字母縮寫詞來回傳遞某些屬性名稱,根據Swift API 設計指南,這些名稱應統一為大寫或小寫(取決於位置) )。
例如,名為someURL
的屬性將使用密鑰some_url
進行編碼,但在解碼時,這將轉換為someUrl
。
要解決此問題,您必須手動將該屬性的編碼鍵指定為解碼器期望的字符串,例如在這種情況下的someUrl
(它仍將被編碼器轉換為some_url
):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(這並不能嚴格回答您的具體問題,但鑒於此問答的規范性質,我覺得值得包括在內)
在 Swift 4.1 中,您可以利用JSONEncoder
和JSONDecoder
上的自定義鍵編碼/解碼策略,允許您提供自定義函數來映射編碼鍵。
您提供的函數采用[CodingKey]
,它表示編碼/解碼中當前點的編碼路徑(在大多數情況下,您只需要考慮最后一個元素;即當前鍵)。 該函數返回一個CodingKey
,它將替換此數組中的最后一個鍵。
例如, lowerCamelCase
屬性名稱的UpperCamelCase
JSON 鍵:
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
}
}
}
您現在可以使用.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"}
並使用.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")
使用 Swift 4.2,根據您的需要,您可以使用以下 3 種策略之一,以使您的模型對象自定義屬性名稱與您的 JSON 鍵匹配。
當您使用以下實現聲明符合Codable
(可Decodable
和可Encodable
協議)的結構時...
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
}
...編譯器會自動為您生成符合CodingKey
協議的嵌套枚舉。
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
}
}
因此,如果序列化數據格式中使用的鍵與數據類型中的屬性名稱不匹配,您可以手動實現此枚舉並為所需情況設置適當的rawValue
。
下面的例子展示了如何做:
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
}
}
編碼(用“zip_code”JSON 密鑰替換zip
屬性):
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"}
*/
解碼(用zip
屬性替換“zip_code”JSON 密鑰):
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")
*/
如果您的 JSON 具有蛇形鍵,並且您想將它們轉換為模型對象的駱駝形屬性,則可以將JSONEncoder
的keyEncodingStrategy
和JSONDecoder
的keyDecodingStrategy
屬性設置為.convertToSnakeCase
。
下面的例子展示了如何做:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
編碼(將駱駝殼屬性轉換為蛇殼 JSON 密鑰):
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"}
*/
解碼(將蛇型 JSON 密鑰轉換為駱駝型屬性):
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")
*/
如有必要, JSONEncoder
和JSONDecoder
允許您設置自定義策略以使用JSONEncoder.KeyEncodingStrategy.custom(_:)
和JSONDecoder.KeyDecodingStrategy.custom(_:)
映射編碼鍵。
以下示例顯示了如何實現它們:
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
}
}
編碼(將小寫首字母屬性轉換為大寫首字母 JSON 鍵):
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"}
*/
解碼(將大寫首字母 JSON 鍵轉換為小寫首字母屬性):
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")
*/
資料來源:
我所做的是創建自己的結構,就像您從 JSON 中獲得的數據類型一樣。
像這樣:
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
}
在此之后,您需要使用CodingKey
創建擴展decodable
的相同struct
擴展和相同struct
的enum
,然后您需要使用此枚舉及其鍵和數據類型來初始化解碼器(鍵將來自枚舉,數據類型將來自或說從結構本身引用)
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 = ""
}
}
}
您需要根據需要在此處更改每個鍵和數據類型,並將其與解碼器一起使用。
通過使用CodingKey,您可以在可編碼或可解碼協議中使用自定義密鑰。
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
} }
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.