web-dev-qa-db-ja.com

Swift 4 Codable;単一のルートレベルキーでオブジェクトをデコードする方法

JSONデータでSwift 4 Codableプロトコルを使用しています。データは、プロパティIを含むオブジェクト値を持つルートレベルに単一のキーがあるようにフォーマットされています次のような必要性:

{
  "user": {
    "id": 1,
    "username": "jdoe"
  }
}

Userキーをデコードできるuser構造体があります。

struct User: Codable {
  let id: Int
  let username: String
}

idusernameuserのプロパティであり、ルートレベルではないため、次のようなラッパータイプを作成する必要がありました。

struct UserWrapper: Codable {
  let user: User
}

その後、UserWrapperを介してJSONをデコードできます。また、Userもデコードされます。私は持っているすべての型に追加のラッパーが必要になるので、多くの冗長なコードのようです。このラッパーパターンを回避する方法や、この状況を処理するより正確でエレガントな方法はありますか?

31
Joshua Breeden

辞書とユーザーの組み合わせを使用してデコードし、ユーザーオブジェクトを抽出できます。例えば.

struct User: Codable {
    let id: Int
    let username: String
}

let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
39
Ollie

Ollieの答えは間違いなくこのケースに最適な方法ですが、呼び出し元に知識をプッシュするため、望ましくない場合があります。また、あまり柔軟性がありません。私はまだそれが素晴らしい答えであり、まさにあなたがここで望むものだと思いますが、これはカスタム構造エンコーディングを探求するニースの簡単な例です。

これをどのように正しく機能させることができますか:

let user = try? JSONDecoder().decode(User.self, from: json)

デフォルトの適合性は使用できなくなりました。独自のデコーダを構築する必要があります。それは少し退屈ですが、難しくはありません。まず、構造をCodingKeysにエンコードする必要があります。

struct User {
    let id: Int
    let username: String

    enum CodingKeys: String, CodingKey {
        case user // The top level "user" key
    }

    // The keys inside of the "user" object
    enum UserKeys: String, CodingKey {
        case id
        case username
    }
}

それで、ネストされたコンテナを引き出すことでUserを手でデコードできます:

extension User: Decodable {
    init(from decoder: Decoder) throws {

        // Extract the top-level values ("user")
        let values = try decoder.container(keyedBy: CodingKeys.self)

        // Extract the user object as a nested container
        let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)

        // Extract each property from the nested container
        id = try user.decode(Int.self, forKey: .id)
        username = try user.decode(String.self, forKey: .username)
    }
}

しかし、この問題に対するOllieの方法は絶対にやります。

詳細については、「 カスタムタイプのエンコードおよびデコード 」を参照してください。

39
Rob Napier

もちろん、独自のカスタムデコード/エンコードをいつでも実装できますが、この単純なシナリオでは、ラッパータイプの方がはるかに優れたソリューションですIMO;)

比較のために、カスタムデコードは次のようになります。

struct User {
    var id: Int
    var username: String

    enum CodingKeys: String, CodingKey {
        case user
    }

    enum UserKeys: String, CodingKey {
        case id, username
    }
}

extension User: Decodable {
    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        let user = try values.nestedContainer(keyedBy: UserKeys.self, forKey: .user)
        self.id = try user.decode(Int.self, forKey: .id)
        self.username = try user.decode(String.self, forKey: .username)
    }
}

エンコードもサポートする場合は、Encodableプロトコルに準拠する必要があります。前にも言ったように、単純なUserWrapperの方がはるかに簡単です;)

10
Paulo Mattos

このようなことを容易にするCodableのヘルパー拡張機能を作成しました。

https://github.com/evermeer/Stuff#codable を参照してください

これにより、次のようにユーザーオブジェクトのインスタンスを作成できます。

    let v = User(json: json, keyPath: "user")

元のUser構造体を変更する必要はなく、ラッパーも必要ありません。

5
Edwin Vermeer