こんにちは。
withでiOS開発を行っている @zrn-ns です。
APIから返されたモデルをSwiftの Decodable
適合のモデルでデコードする処理を書いているとき、書き方に少し悩んだので、その内容を簡単にまとめておきます。
検証環境
- Xcode 14.3.1
- Swift 5.8.1
フラットなJSONをネストしたモデルでデコードするには
今回実装方法に少し悩んだのは、プロパティがフラットに並んだJSONを、構造化されたSwiftのモデルにマッピングする処理です。
文章だけだとわかりにくいので、具体例を示します。
【前提】普通のJSONのデコード処理
以下のようなユーザ情報の一覧情報のJSON文字列を、モデルにマッピングすることを考えます。
let usersJsonData1 = """
[
{
"id": 1,
"name": "Mario",
"age": 26,
},
{
"id": 2,
"name": "Bowser",
"age": 34,
}
]
""".data(using: .utf8)!
これは以下のような Decodable
適合のモデル( SimpleUserResponse
)を書けばデコード可能です。
struct SimpleUserResponse: Decodable {
let id: Int
let name: String
let age: Int
}
let simpleUsersResponse = try JSONDecoder().decode([SimpleUserResponse].self, from: jsonData)
【本題】フラットなJSONをネストしたモデルでデコードする
ユーザ情報を返すAPIが↑のエンドポイントだけならいいですが、エンドポイントによってユーザのモデルの一部が変化する場合、どうしたらよいでしょうか。
例えば、一部のAPIでは introduction
や is_favorite
のような追加の属性が返ってくる場合などです。
let usersJsonData2 = """
[
{
"id": 1,
"name": "Mario",
"age": 26,
"introduction": "It's me!",
"is_favorite": true,
},
{
"id": 2,
"name": "Bowser",
"age": 34,
"introduction": "Gwah ha ha ha!",
"is_favorite": false,
}
]
""".data(using: .utf8)!
この場合 introduction
と is_favorite
以外のパラメータは、最初に実装した SimpleUserResponse
と共通なので、Responseモデルは共通化したいですよね。
この場合、以下のように追加パラメータを切り出したモデル(DetailAttrResponse
)を別で用意します。
/// ユーザの詳細情報をまとめたモデル
struct DetailAttrResponse: Decodable {
let introduction: String
let is_favorite: Bool
}
この DetailAttrResponse
と、先程定義した SimpleUserResponse
を組み合わせて、新しい Decodable
適合モデルを作ります。
struct DetailUserResponse: Decodable {
let simpleUser: SimpleUserResponse
let userDetail: DetailAttrResponse
init(from decoder: Decoder) throws {
self.simpleUser = try SimpleUserResponse(from: decoder)
self.userDetail = try DetailAttrResponse(from: decoder)
}
}
let detailUsersResponse = try JSONDecoder().decode([DetailUserResponse].self, from: usersJsonData2)
init(from:)
の部分がミソで、 SimpleUserResponse
と DetailAttrResponse
それぞれの init(from:)
を呼び出すことで、フラットであったユーザ情報を、複数のモデルそれぞれに分離してデコードする事ができます。
どういう場面で役に立つのか
プロジェクトによって、サーバの負荷軽減や情報の秘匿のためにエンドポイントによって返却する属性を変化させたいことがあると思いますが、そのような場合に極めてシンプルな形で処理を共通化させることができます。
別解として protocol
を使った実装も可能ですが、こちらは記述が増えて保守工数が増えてしまうので、(適材適所ではありますが)あまりオススメしません。
最後に
Decodableを使ってなるべくシンプルに、CodingKeys等使わずに実装共通化するにはどうしたら良いかを考えた結果、今回の記事を書くに至りました。
今回の記事とは逆の話で、「ネストしたJSONをフラットな構造体にマッピングする」方法については、 @aokiplayer さんが記事にまとめてらっしゃるので、そちらをご参照ください。
今回の記事のコードは playground としてこちらのリポジトリにアップしてありますので、動作確認したい方はどうぞ。
PR
withではエンジニアの採用を行っています。
興味がありましたら、ぜひお問い合わせください!