iOS
Swift
swift4
Codable

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

More than 1 year has passed since last update.

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)


参考