簡體   English   中英

如何在 Swift 4 的可解碼協議中使用自定義鍵?

[英]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需求:

  1. 類型符合Encodable ,其性質都是Encodable得到一個自動生成的String -backed CodingKey枚舉映射屬性案例名稱。 同樣對於所有屬性都是Decodable Decodable類型

  2. 屬於 (1) 的類型-以及手動提供CodingKey enum (直接命名為CodingKeys ,或通過typealias )的類型,其情況按名稱將 1 對 1 映射到可Encodable / 可Decodable屬性- 自動合成init(from:)並適當地encode(to:) ,使用這些屬性和鍵

  3. 如果需要,既不屬於 (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 ,則可以利用JSONEncoderJSONDecoder上的關鍵編碼/解碼策略,以便在camelCasesnake_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
}

(這並不能嚴格回答您的具體問題,但鑒於此問答的規范性質,我覺得值得包括在內)

自定義自動 JSON 鍵映射

在 Swift 4.1 中,您可以利用JSONEncoderJSONDecoder上的自定義鍵編碼/解碼策略,允許您提供自定義函數來映射編碼鍵。

您提供的函數采用[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 鍵匹配。


#1. 使用自定義編碼鍵

當您使用以下實現聲明符合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")
 */

#2. 使用snake case到camel case的關鍵編碼策略

如果您的 JSON 具有蛇形鍵,並且您想將它們轉換為模型對象的駱駝形屬性,則可以將JSONEncoderkeyEncodingStrategyJSONDecoderkeyDecodingStrategy屬性設置為.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")
 */

#3. 使用自定義鍵編碼策略

如有必要, JSONEncoderJSONDecoder允許您設置自定義策略以使用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擴展和相同structenum ,然后您需要使用此枚舉及其鍵和數據類型來初始化解碼器(鍵將來自枚舉,數據類型將來自或說從結構本身引用)

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM