LoginSignup
0
1

【Swift】フラットなJSONをネストしたモデルでデコードする【Decodable】

Last updated at Posted at 2023-09-13

こんにちは。
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では introductionis_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)!

この場合 introductionis_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:) の部分がミソで、 SimpleUserResponseDetailAttrResponse それぞれの init(from:) を呼び出すことで、フラットであったユーザ情報を、複数のモデルそれぞれに分離してデコードする事ができます。

どういう場面で役に立つのか

プロジェクトによって、サーバの負荷軽減や情報の秘匿のためにエンドポイントによって返却する属性を変化させたいことがあると思いますが、そのような場合に極めてシンプルな形で処理を共通化させることができます。

別解として protocol を使った実装も可能ですが、こちらは記述が増えて保守工数が増えてしまうので、(適材適所ではありますが)あまりオススメしません。

最後に

Decodableを使ってなるべくシンプルに、CodingKeys等使わずに実装共通化するにはどうしたら良いかを考えた結果、今回の記事を書くに至りました。

今回の記事とは逆の話で、「ネストしたJSONをフラットな構造体にマッピングする」方法については、 @aokiplayer さんが記事にまとめてらっしゃるので、そちらをご参照ください。

今回の記事のコードは playground としてこちらのリポジトリにアップしてありますので、動作確認したい方はどうぞ。

PR

withではエンジニアの採用を行っています。
興味がありましたら、ぜひお問い合わせください!

0
1
0

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
0
1