LoginSignup
64
48

More than 5 years have passed since last update.

ルートが配列のJSONをCoadableでカスタムモデルにマッピングする

Posted at

Swift4で新しく追加されたCodableを利用して、ルートが配列のJSONをそのままCodableを適用したモデルの配列にマッピングするのではなく、該当する配列をプロパティとして持つCodableを適用したコンテナ的なモデルにマッピングする方法が見つからず、色々調べた結果、対応方法がわかったので記しておく。

ルートが配列のJSON

例えばGithub APIのGet all usersのレスポンスは

[
  {
    "login": "mojombo",
    "id": 1,
    "url": "https://api.github.com/users/mojombo",
    ...
    "site_admin": false
  },
  {
    "login": "defunkt",
    "id": 2,
    "url": "https://api.github.com/users/defunkt",
    ...
    "site_admin": true
  },
  ...
]

とJSONのルートが配列になっている。

モデル

上記のJSONをアプリで利用するために、Codableを適用したUserモデルを以下のように作成する。

struct User: Codable {
    let id: Int
    let login: String
    let url: String
}

デコード

単純にこのモデルオブジェクトの配列にデコードするのであれば、

let users: [User] = try JSONDecoder().decode([User].self, from: data)

で可能。

コンテナモデル

上記のように配列にデコードしたモデルをアプリ内で受け渡しながら使いまわすよりも、その配列をプロパティとして持つモデルにマッピングしたいことがある。

例えば以下のようなUserResponseというモデル定義する。

struct UserResponse {
    let users: [User]
}

しかし、このUserResponseCodableを適用したもうまくいかない。

struct UserResponse: Codable {
    let users: [User]
}

let response: UserResponse = try JSONDecoder().decode(UserResponse.self, from: data)

// typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead."))

カスタムデコード、エンコードメソッド

そこでDecodableEncodableのメソッドを独自に実装する。

public protocol Decodable {
    public init(from decoder: Decoder) throws
}

public protocol Encodable {
    public func encode(to encoder: Encoder) throws
}
  • Decodable
extension UserResponse: Decodable {
    init(from decoder: Decoder) throws {
        var users: [User] = []
        var unkeyedContainer = try decoder.unkeyedContainer()
        while !unkeyedContainer.isAtEnd {
            let user = try unkeyedContainer.decode(User.self)
            users.append(user)
        }
        self.init(users: users)
    }
}
  • Encodable
extension UserResponse: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        for user in users {
            try container.encode(user)
        }
    }
}

上記のようにDecodableEncodableを実装すればデコード、エンコードともに期待したように動く。

// decode
let response: UserResponse = try JSONDecoder().decode(UserResponse.self, from: data)

// encode
let encoded = try JSONEncoder().encode(response)
let json = String(data: encoded, encoding: .utf8)

参考

64
48
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
64
48