トップ «前の日記(2019-01-22) 最新 次の日記(2019-01-26)» 編集

2019-01-23 [長年日記]

_ 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つ以上になったらこの形式は通用しない。つまり何かあったら

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

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

Tags: Web JSON

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

本日のツッコミ(全4件) [ツッコミを入れる]
_ mather (2019-01-28 13:55)

オブジェクトの属性としてのkeyやidと、任意項目としてのデータの話がごっちゃになっている気がしました。 <br>単に「複数のデータ列挙」を扱うときの話であるという前提なら配列で扱うべきという点には同意なのですが、最初に提示されている例はそれ自体が一つのオブジェクトの表現である場合は正しいと思います。

_ wtnabe (2019-01-28 15:32)

ありがとうございます。 <br> <br>仰る通りなんですけど「本当に順番に依存していないと断言できる?」って場合が稀によくあるので、設計の際にまずはサーバサイドとクライアントサイドでブレが起きない形に揃えておく方が見落としがなくなっていいよね、という話ですね。 <br> <br>意外と簡単に前提だと思っていたことがひっくり返っちゃたり、設計の際に見落としていたことがあとから見つかったりするので、レビューの際にはまずはこっちを基準にしておいて、「自信を持って順番を維持しなくてもよいと断言できる場合」はそれを認めるという形にした方が「安全」と考えるようになったので書き起こした次第です。

_ mather (2019-01-30 01:57)

いや、なんかちょっと言いたいこととずれてる気がします。 <br>端的にいうと、Userという単一のオブジェクトを表現するJSONはオブジェクトであり、Userのリストを表現するならJSONもリストになりますよね?ということです。 <br>まずはそこが大前提であって、リストを表現するのに「順序に依存しないから」という理由でオブジェクトの表記(連想配列)にするのはそもそもやってはいけないと思っています。

_ wtnabe (2019-01-30 07:22)

あーなるほど。それも仰る通りですが、そもそもの構造の話と今回の話は独立して考えることができると思っています。本来の構造の正しさではなくインターフェイスの適切さの話をしていると言えばいいですかね。「もとのデータ構造がどうであっても」API を考える際にはこういうところを注意した方がよいよねという話でして、あくまで中心はデータ構造ではなくインターフェイスの話です。 <br> <br>インターフェイスを適切に設計しそれを守れば、インターフェイスの向こうのことは気にしなくてよくなるので、できるだけそうなるようにしよう、そのためにはこういう serialize がよいよね、という話ですね。