130

DecodableSwift 4 では、プロトコルを介したネイティブ JSON エンコーディングとデコーディングのサポートが導入されました。これにカスタム キーを使用するにはどうすればよいですか?

たとえば、構造体があるとします

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" 
}

にマップされAddressているデコーダーにどのように伝えますか? 新しいプロトコルを使用していると思いますが、これを使用する方法がわかりません。zip_codezipCodingKey

4

4 に答える 4

324

コーディング キーの手動カスタマイズ

あなたの例では、Codableすべてのプロパティもに準拠しているため、自動生成された準拠を得ていますCodable。この適合性により、プロパティ名に単純に対応するキー タイプが自動的に作成されます。これは、単一のキー付きコンテナーとの間でエンコード/デコードするために使用されます。

ただし、この自動生成された準拠の非常に優れた機能の 1 つ、プロトコルに準拠するenum" " と呼ばれるCodingKeys(またはtypealiasこの名前で a を使用する)ネストされた型を定義すると、Swift はこれをキーの型として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
    }
}

列挙型のケース名はプロパティ名と一致する必要があり、これらのケースの生の値は、エンコード/デコード元のキーと一致する必要があります (特に指定しない限り、String列挙型の生の値はケース名と同じになります) )。したがって、zipプロパティは key を使用してエンコード/デコードされます"zip_code"

自動生成Encodable/Decodable適合の正確なルールは、進化の提案(強調鉱山) で詳述されています。

の自動CodingKey要件合成に 加えてenums、特定のタイプのEncodable&Decodable要件も自動合成できます。

  1. Encodableプロパティがすべてに準拠している型Encodableは、自動的に生成された、プロパティをケース名にマッピングする にString裏打ちされた列挙型を取得します。CodingKey同様にDecodable、プロパティがすべてDecodable

  2. (1) に該当する型 — および手動でCodingKey enum(名前付きCodingKeys、直接、または a を介してtypealias) を提供する型で、そのケースは名前によって 1 対 1 でEncodable/Decodableプロパティにマップされます —これらのプロパティとキーを使用して、必要に応じてinit(from:)との自動合成を取得します。encode(to:)

  3. (1) にも (2) にも該当しないタイプは、必要に応じてカスタム キー タイプを提供し、必要に応じて独自のinit(from:)and を提供する必要があり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_caseJSON キーcamelCase

Swift 4.1 では、zipプロパティの名前を に変更すると、 と の間でコーディング キーを自動的に変換するために、 とzipCodeのキー エンコーディング/デコーディング戦略を利用できます。JSONEncoderJSONDecodercamelCasesnake_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")

ただし、この戦略について注意すべき重要な点の 1 つは、 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
}

(これはあなたの特定の質問に厳密に答えるものではありませんが、このQ&Aの標準的な性質を考えると、含める価値があると思います)

カスタムの自動 JSON キー マッピング

Swift 4.1 では、 および でカスタム キーのエンコード/デコード戦略をJSONEncoder利用してJSONDecoder、コーディング キーをマップするカスタム関数を提供できます。

提供する関数は[CodingKey]、エンコード/デコードの現在のポイントのコーディング パスを表す を受け取ります (ほとんどの場合、最後の要素、つまり現在のキーのみを考慮する必要があります)。この関数は、CodingKeyこの配列の最後のキーを置き換える a を返します。

たとえば、プロパティ名のUpperCamelCaseJSON キーは次のとおりです。lowerCamelCase

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")
于 2017-06-06T18:01:25.840 に答える
27

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プロパティを "zip_code" JSON キーに置き換えます):

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_code」JSON キーをzipプロパティに置き換えます):

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。スネーク ケースからキャメル ケースへのキー コーディング戦略の使用

JSON にスネークケースのキーがあり、それらをモデルオブジェクトのキャメルケースのプロパティに変換したい場合は、とJSONEncoderのプロパティを に設定できます。keyEncodingStrategyJSONDecoderkeyDecodingStrategy.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。カスタム キー コーディング戦略の使用

必要に応じて、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")
 */

ソース:

于 2017-07-11T11:11:37.310 に答える