JSONでデータを返すAPIは構造の意味を持たせつつArrayを返そう

すっげー初歩的な話。初歩的すぎて『Web API: The Good Parts』にもたぶん載ってない。

あと envelop の話はあえて無視してます。envelop を付けるべきか否かはこのエントリでは扱いません。あくまでデータの話に限定しています。

まとめ

自分でお手軽に作る時に key-value のデータをそのまま返しちゃう場合があるけど、これはやめた方がよい。

つい

{
  "key1": "val1",
  "key2": "val2",
  ..
}

こういうことしたくなる。楽だけどこれはやめた方がよい。

[
  {
    "key": "key1",
    "val": "val1",
  },
  {
    "key": "key2",
    "val": "val2"
  },
  ..
]

この方がよい。

例えば id と name を持つデータなら

{
  1234: "alice",
  2345: "bob"
}

ではなく

[
  {
    "id":   1234,
    "name": "alice"
  },
  {
    "id":   2345,
    "name": "bob"
  }
]

の方がよい。

理由1 - 順番が意図せず変わる

まず Object や Hash, Map などの構造をそのまま JSON に serialize する際に順番がどうなるか分からない。

本来順番に依存しない構造なのだから当たり前なのだが、例えば PHP の場合は

  • array は順番の維持される map のような何か
  • json_encode は key が数値だと array の順番を並べ直して encode する

というコンボが発生して、結果として json_encode する前と後で順番が変わってしまう。これでバグが生まれると割ときついですね。盲点。あくまで PHP の array や Ruby 1.9 以降の Hash がたまたま順番を維持してくれるだけという簡単な事実を忘れてはいけない。

で、これが大事なところなのだが、

本当に順番に依存しないかどうかはそのデータを serialize/deserialize する部分だけでは分からない。それは UI や他の DBMS などの「インターフェイス」の部分で何を期待するかで決まる。

だから本当は元のデータ構造の段階から順番に期待する処理は順番が確実に維持される構造にしておくべき。つまり

{}

ではなく

[
  {},
  {}
]

の方がよい。こうしておけばサーバ側とクライアント側で扱うデータ構造が変わってしまうことがないし、処理がブレることもない。

理由2 - なんの項目を扱っているのか分からない

ただし、上のように Ruby で言う Hash の Array みたいな構造にしただけではまだデータを扱いにくい。2項目しかないとつい

[
  {
    "key1": "val1"
  },
  {
    "key2": "val2"
  },
  ..
]

みたいにしたくなるが、これはデータを作るのも利用するのも同じコードの中ならまだいいけど、距離が離れると意味が分からなくなるのでやめた方がよい。

「この key はどういう意味なのか? value は何か?」がデータの中に存在しないし、だいたいの言語で key を取り出す方法も value を取り出す方法もない。1

で、結局 record[0], record[1] みたいなものをベタ書きすることになる。

これはつらいのでやめるべき。

理由3 - 変更に弱い

理由2 で挙げた「避けた方がよい形式」だが、項目が3つ以上になったらこの形式は通用しない。つまり何かあったら

  • データ構造をやや大きく変更せざるを得ない
  • データ構造が変わるとクライアント側のコードへの影響が大きい

だったら最初から全部同じ方法で扱うようにした方がよい。

  1. keys や values は取り出せるが、一つしかないのは分かっているのにいったん Array が手に入ってどんな構造を扱っているかも分かりにくくなる。 

More