15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ClassiAdvent Calendar 2016

Day 3

アプリエンジニアからサーバサイドエンジニアへの JSON オブジェクトに関するお願い

Posted at

Classi Advent Calendar 2016 3 日目です!

API レスポンスの JSON オブジェクトが統一されいないとクライアントサイドでのハンドリングが煩雑になるため、サーバサイド API を作る時に JSON のキー名は適切につけて欲しい、という内容です。

ここでは、クライアントサイドは Swift で書いていきます。

JSON マッピング

例えば users/1 という API が以下のレスポンスを返すとします。

{
  "user": {
    "id": 1,
    "name": "渡辺曜",
    "age": 16
  }
}

このレスポンスをハンドリングするために、 JSON マッピングをしていくことになります。
ここでは Himotoki を使っています。

struct User: Decodable {
    let id: Int
    let name: String
    let age: Int

    static func decode(_ e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            name: e <| "name",
            age: e <| "age"
        )
    }
}

これで、JSON を Swift で扱えるようになりました。
user.id, user.name などでアクセスできるようになり、型も担保されいい感じです。 :blush:

別の API の user オブジェクト

users/1/best_friend という API のレスポンスは以下のようになっていました。

{
  "user": {
    "id": 2,
    "first_name": "千歌",
    "last_name": "高海"
  }
}

なんということでしょう、 user というキーは同じ名前なのに first_name と last_name という新しいものがあり、 age と name は消えてしまいました。 :scream:
このレスポンスをマッピングするとこうなります。

struct User: Decodable {
    let id: Int
    let firstName: String
    let lastName: String

    static func decode(_ e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            firstName: e <| "first_name",
            lastName: e <| "last_name"
        )
    }
}

しかし困ったことに User struct が 2 つ生まれてしまい、名前が衝突してしまいました... :cry:

クライアント側で解決するには?

struct 名を UserOfFriend など変えるか、 optional を使うしかありません。

struct 例:

struct UserOfFriend: Decodable { ...

名前は衝突しなくなりました。
しかし、 JSON のキー名とオブジェクトの属性名が違ってしまいます。 :angry:

optional 例:

struct User: Decodable {
    let id: Int
    let firstName: String?
    let lastName: String?
    let name: String?
    let age: Int?

    static func decode(_ e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            firstName: e <| "first_name",
            lastName: e <| "last_name",
            name: e <| "name",
            age: e <| "age"
        )
    }
}

こうすれば users/1 でも users/1/best_friend でも同じ User として利用できます。
ですが、これだと毎回 nil かどうかの確認をしなければならず、よくありません。
面倒くさくて nil チェックを怠る野蛮な人間が今後出てくる可能性もあります。 :cop:

どうしてほしいか

クライアントで頑張には限界があるため、オブジェクトの中身が違うなら名前を変えてレスポンスを返して欲しいです。
userfriend のように JSON キー名を変えてもらうだけで、 UserFriend と別の型を用意することができました! :tada:

api json mapping
users/1 {
  "user": {
    "id": 1,
    "name": "渡辺曜",
    "age": 16
  }
}
struct User: Decodable {
    let id: Int
    let name: String
    let age: Int

    static func decode(_ e: Extractor) throws -> User {
        return try User(
            id: e <| "id",
            name: e <| "name",
            age: e <| "age"
        )
    }
}
users/1/best_friend {
  "friend": {
    "user_id": 2,
    "first_name": "千歌",
    "last_name": "高海"
  }
}
struct Friend: Decodable {
    let id: Int
    let firstName: String
    let lastName: String

    static func decode(_ e: Extractor) throws -> Friend {
        return try Friend(
            id: e <| "id",
            lastName: e <| "last_name",
            firstName: e <| "first_name"
        )
    }
}

最後に

JSON オブジェクトの命名を適切にしていただくだけで、クライアント側の負荷が減ります :smiley:

私も API を作っていると同じ名前で別のオブジェクトを生成してしまうことがあるので、そうはならないように気をつけたり、 JSON を生成する処理をキーごとに共通化しています。
Rails でいうと、 jbuilderpartial を細かく分割するなどで対応出来ると思います。
(ただし jbuilderpartial は重いのであまり使うなという話も...)

配列なら users のように複数形にしてほしい、必須パラメータなら URLParameter でなく path に含めてほしいなどいろいろあります。
サーバサイドでやってほしいこと、クライアントサイドでやるべきことの認識を合わせ、互いに定時退社できるよう協力していきましょう!!

15
13
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
15
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?