APIレスポンスのルートは配列じゃなくオブジェクトにする

  • API
  • JSON
  • Backend

この記事は、構成と文章の整理にAIを使っています。
あくまで読みやすくするための補助で、内容そのものをAIにすべて任せて書いているわけではありませんが、ところどころ相談しながら整えています。

APIのJSONレスポンス、ルートをいきなり配列で返すのは避けたほうがいい。

[
{ "id": 1, "name": "example" },
{ "id": 2, "name": "example" }
]

これをやると、あとからページング情報や件数、エラー情報を足したくなったときに構造を壊すしかなくなる。なのでルートはオブジェクトにして、中に data を持たせるのが定番。

{
"data": [
{ "id": 1, "name": "example" },
{ "id": 2, "name": "example" }
],
"meta": {
"page": 1,
"perPage": 10,
"total": 100
}
}

エラー時も同じ形でラップできる。

{
"error": {
"code": 400,
"message": "Invalid request parameters"
}
}

セキュリティ理由は今はもう主目的じゃない

このパターン、「JSONハイジャック対策」という文脈で語られることが多い。2007年頃、Array コンストラクタを書き換えてルート配列JSONの中身を盗み見る攻撃が実際にあって、Gmailなどが影響を受けたらしい。ただこの攻撃は当時のブラウザ(Firefox 2.0以下など)に限った話で、その後すべてのブラウザで修正済みとのこと。実機で再現できるかは未確認。

なので今この構成を採用する理由としては、セキュリティよりも「拡張性」の比重のほうが大きい。ルートをオブジェクトにしておけば、meta や error を後付けできる。ルートが配列だとそれができない。ここが実務上の本題。

リストが複数あるときはdata配下でキーを分ける

1つのレスポンスに複数のリストを含めたい場合は、data の下にキーごとに分けるのが素直。

{
"data": {
"users": [
{ "id": 1, "name": "example" }
],
"posts": [
{ "id": 101, "title": "example" }
]
},
"meta": {
"usersCount": 1,
"postsCount": 1
}
}

users と posts を混ぜて1つの配列にしないことで、クライアント側がどのリストが何を表すか型で判断できる。メタ情報もリストごとに持たせやすい。

ただしこの構造に唯一の正解があるわけではない。JSON:APIのように data キーを規約として定めている仕様もあるが、大半はチームやプロジェクトの慣習として採用されているだけ。守るべきは「ルートをオブジェクトにして、拡張の余地を残す」という考え方の部分で、キーの命名やネストの深さは案件ごとに決めればいい。