JSONデータでSwift 4 Codable
プロトコルを使用しています。データは、プロパティIを含むオブジェクト値を持つルートレベルに単一のキーがあるようにフォーマットされています次のような必要性:
{
"user": {
"id": 1,
"username": "jdoe"
}
}
User
キーをデコードできるuser
構造体があります。
struct User: Codable {
let id: Int
let username: String
}
id
とusername
はuser
のプロパティであり、ルートレベルではないため、次のようなラッパータイプを作成する必要がありました。
struct UserWrapper: Codable {
let user: User
}
その後、UserWrapper
を介してJSONをデコードできます。また、User
もデコードされます。私は持っているすべての型に追加のラッパーが必要になるので、多くの冗長なコードのようです。このラッパーパターンを回避する方法や、この状況を処理するより正確でエレガントな方法はありますか?
辞書とユーザーの組み合わせを使用してデコードし、ユーザーオブジェクトを抽出できます。例えば.
struct User: Codable {
let id: Int
let username: String
}
let decoder = JSONDecoder()
let userDictionary = try decoder.decode([String: User].self, from: jsonData)
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の方法は絶対にやります。
詳細については、「 カスタムタイプのエンコードおよびデコード 」を参照してください。
もちろん、独自のカスタムデコード/エンコードをいつでも実装できますが、この単純なシナリオでは、ラッパータイプの方がはるかに優れたソリューションです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
の方がはるかに簡単です;)
このようなことを容易にするCodableのヘルパー拡張機能を作成しました。
https://github.com/evermeer/Stuff#codable を参照してください
これにより、次のようにユーザーオブジェクトのインスタンスを作成できます。
let v = User(json: json, keyPath: "user")
元のUser構造体を変更する必要はなく、ラッパーも必要ありません。