Swift 4 の Decodable プロトコルでカスタムキーを使用するにはどうすればいいですか? 質問する

Swift 4 の Decodable プロトコルでカスタムキーを使用するにはどうすればいいですか? 質問する

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

Addressそのzip_codeマップ上のデコーダーに指示するにはどうすればよいでしょうかzip? 新しいプロトコルを使用していると思いますCodingKeyが、これをどのように使用すればよいかわかりません。

ベストアンサー1

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

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

しかし、本当にこの自動生成された適合性の優れた特徴は、 " "enumと呼ばれる型にネストされた を定義すると(またはこの名前で を使用すると)、CodingKeystypealiasCodingKeyプロトコル – 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、特定のタイプに対してEncodableDecodable要件を自動的に合成できます。

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

  2. (1)に該当するタイプCodingKey enumおよび手動で( という名前でCodingKeys、直接、または を介し​​て)提供する型。そのケースは、名前によって/プロパティtypealiasに 1 対 1 でマッピングされます。EncodableDecodable—それらのプロパティとキーを使用して、必要に応じて自動的に合成し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_caseJSONキーcamelCase

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

おすすめ記事