web-dev-qa-db-ja.com

Swift 4デコード時間まで認識されないキーでデコード可能

Swift 4 Decodableプロトコルは、実行時まで名前がわからないキーを含む辞書をどのように処理しますか?例えば:

  [
    {
      "categoryName": "Trending",
      "Trending": [
        {
          "category": "Trending",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    },
    {
      "categoryName": "Comedy",
      "Comedy": [
        {
          "category": "Comedy",
          "trailerPrice": "",
          "isFavourit": null,
          "isWatchlist": null
        }
      ]
    }
  ]

ここには辞書の配列があります。最初のキーにはcategoryNameおよびTrendingキーがあり、2番目のキーにはcategoryNameおよびComedyキーがあります。 categoryNameキーの値は、2番目のキーの名前を示します。 Decodableを使用してどのように表現しますか?

48
matt

重要なのは、CodingKeysプロパティの定義方法です。最も一般的にはenumですが、CodingKeyプロトコルに準拠するものであれば何でもかまいません。また、動的キーを作成するには、静的関数を呼び出すことができます。

struct Category: Decodable {
    struct Detail: Decodable {
        var category: String
        var trailerPrice: String
        var isFavorite: Bool?
        var isWatchlist: Bool?
    }

    var name: String
    var detail: Detail

    private struct CodingKeys: CodingKey {
        var intValue: Int?
        var stringValue: String

        init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
        init?(stringValue: String) { self.stringValue = stringValue }

        static let name = CodingKeys.make(key: "categoryName")
        static func make(key: String) -> CodingKeys {
            return CodingKeys(stringValue: key)!
        }
    }

    init(from coder: Decoder) throws {
        let container = try coder.container(keyedBy: CodingKeys.self)
        self.name = try container.decode(String.self, forKey: .name)
        self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
    }
}

使用法:

let jsonData = """
  [
    {
      "categoryName": "Trending",
      "Trending": [
        {
          "category": "Trending",
          "trailerPrice": "",
          "isFavourite": null,
          "isWatchlist": null
        }
      ]
    },
    {
      "categoryName": "Comedy",
      "Comedy": [
        {
          "category": "Comedy",
          "trailerPrice": "",
          "isFavourite": null,
          "isWatchlist": null
        }
      ]
    }
  ]
""".data(using: .utf8)!

let categories = try! JSONDecoder().decode([Category].self, from: jsonData)

(JSONのisFavouritisFavouriteに変更したのは、スペルが間違っていると思ったからです。そうでない場合は、コードを簡単に調整できます)

47
Code Different

CodingKeysオブジェクトとして機能するカスタム構造体を作成し、指定したキーを抽出するように文字列で初期化できます。

private struct CK : CodingKey {
    var stringValue: String
    init?(stringValue: String) {
        self.stringValue = stringValue
    }
    var intValue: Int?
    init?(intValue: Int) {
        return nil
    }
}

したがって、目的のキーが何であるかがわかったら、(init(from:)オーバーライドで:

let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)

私がやったことは、デコーダからtwoコンテナを作成することです。1つは標準のCodingKeys列挙を使用して"categoryName"キーの値を抽出します、およびCK構造体を使用して、先ほど学習した名前のキーの値を抽出する別のコード:

init(from decoder: Decoder) throws {
    let con = try! decoder.container(keyedBy: CodingKeys.self)
    self.categoryName = try! con.decode(String.self, forKey:.categoryName)
    let key = self.categoryName
    let con2 = try! decoder.container(keyedBy: CK.self)
    self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}

ここに、私のDecodable構造体全体を示します。

struct ResponseData : Codable {
    let categoryName : String
    let unknown : [Inner]
    struct Inner : Codable {
        let category : String
        let trailerPrice : String
        let isFavourit : String?
        let isWatchList : String?
    }
    private enum CodingKeys : String, CodingKey {
        case categoryName
    }
    private struct CK : CodingKey {
        var stringValue: String
        init?(stringValue: String) {
            self.stringValue = stringValue
        }
        var intValue: Int?
        init?(intValue: Int) {
            return nil
        }
    }
    init(from decoder: Decoder) throws {
        let con = try! decoder.container(keyedBy: CodingKeys.self)
        self.categoryName = try! con.decode(String.self, forKey:.categoryName)
        let key = self.categoryName
        let con2 = try! decoder.container(keyedBy: CK.self)
        self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
    }
}

そして、ここにテストベッドがあります。

    let json = """
      [
        {
          "categoryName": "Trending",
          "Trending": [
            {
              "category": "Trending",
              "trailerPrice": "",
              "isFavourit": null,
              "isWatchlist": null
            }
          ]
        },
        {
          "categoryName": "Comedy",
          "Comedy": [
            {
              "category": "Comedy",
              "trailerPrice": "",
              "isFavourit": null,
              "isWatchlist": null
            }
          ]
        }
      ]
    """
    let myjson = try! JSONDecoder().decode(
        [ResponseData].self, 
        from: json.data(using: .utf8)!)
    print(myjson)

次に、printステートメントの出力を示します。これは、構造体を正しく設定したことを証明しています。

[JustPlaying.ResponseData(
    categoryName: "Trending", 
    unknown: [JustPlaying.ResponseData.Inner(
        category: "Trending", 
        trailerPrice: "", 
        isFavourit: nil, 
        isWatchList: nil)]), 
 JustPlaying.ResponseData(
    categoryName: "Comedy", 
    unknown: [JustPlaying.ResponseData.Inner(
        category: "Comedy", 
        trailerPrice: "", 
        isFavourit: nil, 
        isWatchList: nil)])
]

もちろん、実生活では間違いなくいくつかのエラー処理があります。


EDIT後で(CodeDifferentの答えのおかげで)2つのコンテナが必要ないことに気付きました。 CodingKeys列挙型を削除でき、CK構造体はすべての作業を実行できます!汎用キーメーカーです。

init(from decoder: Decoder) throws {
    let con = try! decoder.container(keyedBy: CK.self)
    self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
    let key = self.categoryName
    self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
5
matt

また、この質問をしました。このjsonが最終的に思いついたのは次のとおりです。

let json = """
{
    "BTC_BCN":{
        "last":"0.00000057",
        "percentChange":"0.03636363",
        "baseVolume":"47.08463318"
    },
    "BTC_BELA":{
        "last":"0.00001281",
        "percentChange":"0.07376362",
        "baseVolume":"5.46595029"
    }
}
""".data(using: .utf8)!

このような構造を作成します。

struct Pair {
    let name: String
    let details: Details

    struct Details: Codable {
        let last, percentChange, baseVolume: String
    }
}

デコード:

if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {

    var pairs: [Pair] = []
    for (name, details) in pairsDictionary {
        let pair = Pair(name: name, details: details)
        pairs.append(pair)
    }

    print(pairs)
}

Pair.details.baseVolumeではなく、pair.baseVolumeを呼び出すこともできます。

struct Pair {
    ......
    var baseVolume: String { return details.baseVolume }
    ......

または、カスタムinitを記述します。

struct Pair {
    .....
    let baseVolume: String
    init(name: String, details: Details) {
         self.baseVolume = details.baseVolume
    ......
1
vbb