Swift4からのCodableのエンコード処理少し迷ったところがあったのでメモ。
一対一形式のJSONをEncodableに対応
下記のようなKeyとValueが一対一になっているJSONがあるとします。
ログインユーザーを表すAPIのリクエストデータを想定しています。
{
"id": 0,
"first_name": "tommy",
"user_token": "AAAAAA"
}
上記のJSONをCodableでエンコードすると以下のようになります。
struct User: Encodable {
var id: Int
var firstName: String
var userToken: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case userToken = "user_token"
}
}
let user = try! JSONEncoder().encode(User(id: 0, firstName: "tommy", userToken: "AAAAAA"))
print(String(data: user, encoding: String.Encoding.utf8)!)
CodingKeys
でJSONキーを指定すれば、エンコードができます。
便利。
Jsonの構造が変わった場合
困ったのがこんな場合の時。
サーバー側の仕様が変わって先程のJSONに「user
というトップのキーを追加する」仕様が加わったとします。
{
"user": {
"id": 0,
"first_name": "tommy",
"user_token": "AAAAAA"
}
}
この場合にSwiftコードはどうするべきか。
Encodableに適応している型であればエンコードができるので新しい型を作るのがまず考えられます。
RootUser
型を新しく作ってみます。
struct RootUser: Encodable {
var user: User
}
let user3 = try! JSONEncoder().encode(RootUser(user:User(id: 0, firstName: "tommy", userToken: "AAAAAA")))
print(String(data: user3, encoding: String.Encoding.utf8)!)
これでも期待するJSONデータを作ることができます。
が、Jsonの形式が変わる度に新しい型を作るのはちょっと面倒。
マニュアルエンコードでネストさせる
JSONの形式がSwiftの型と一致しない場合独自にエンコード実装をすることができます。
こうすればJSON形式が変わってもその度にSwiftの方では型を新しく追加する必要はありません。
struct User: Encodable {
var id: Int
var firstName: String
var userToken: String
enum CodingKeys: String, CodingKey {
case user
}
private enum NestedKeys: String, CodingKey {
case id
case firstName = "first_name"
case userToken = "user_token"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var nestObject = container.nestedContainer(keyedBy: NestedKeys.self, forKey: .user)
try nestObject.encode(id, forKey: .id)
try nestObject.encode(firstName, forKey: .firstName)
try nestObject.encode(userToken, forKey: .userToken)
}
}
let user = try! JSONEncoder().encode(User(id: 0, firstName: "tommy", userToken: "AAAAAA"))
print(String(data: user, encoding: String.Encoding.utf8)!)
CodingKeys
にuser
というキーを指定して、さらにネストされた各キーをNestedKeys
で指定します。
func encode(to encoder:)
を実装することで独自にエンコードすることができます。
encoder.container
でトップのコンテナを取得した後に、container.nestedContainer
メソッドを実行することで、ネストされたObjectを取得できます。
あとはencode
メソッドで各プロパティをエンコードすればよいです。
結果
{
"user": {
"id": 0,
"first_name": "tommy",
"user_token": "AAAAAA"
}
}
まとめ
独自でEncodableでエンコードする方法を紹介しました。
Encodableが自動で行ってくれるのもいいのですが、今回のように「ネストされたデータ形式は今まで同じだから型を新しく作るまででもない」っていう時は上手くハマると思います。