1. ホーム
  2. json

[解決済み] Swift 4のDecodableプロトコルでカスタムキーを使うには?

2022-07-12 14:16:25

質問

Swift 4 では、JSON のネイティブなエンコードとデコードのサポートが導入されました。 Decodable プロトコルによるネイティブな 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_codezip ? 私は、あなたが新しい CodingKey プロトコルを使用していると思いますが、これをどのように使用するのかがわかりません。

どのように解決するのですか?

コーディング キーを手動でカスタマイズする

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

しかし、1 つの は本当に この自動生成されたコンフォーマンスの優れた点は、ネストされた enum という型の中で定義すると CodingKeys という型を作成します(あるいは typealias に準拠し、この名前を持つ) CodingKey プロトコルに準拠するもの - Swift は自動的に この をキーの型として使用します。したがって、これはあなたのプロパティがエンコード/デコードされるキーを簡単にカスタマイズすることができます。

ということは、こう言えばいいんです。

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 プロパティはキー "zip_code" .

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

<ブロッククオート

に加えて、自動 CodingKey の要件合成に加え enums , Encodable & Decodable を自動的に合成することができます。 を自動的に合成することもできます。

  1. に準拠した型 Encodable に準拠した型であり、そのプロパティはすべて Encodable を取得すると、自動的に生成された String -バックされる CodingKey enum マッピング プロパティを大文字と小文字にマッピングします。同様に 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 のJSONキー camelCase プロパティ名

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
}


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

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

Swift 4.1 では、カスタムキーエンコーディング/デコーディングストラテジーを利用するために JSONEncoderJSONDecoder であり、コーディングキーをマッピングするためのカスタム関数を提供することができます。

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

例えば UpperCamelCase のJSONキーは 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")