Cocos2d-x で JSON フォーマットを扱うためのライブラリを探すと、大抵最初に見つかるのは spine/Json.h です。

Cocos2d-x に組み込まれているため、何かのライブラリを追加で入れる必要はありません。 使っている人も多いため、ちょっと検索すれば使い方も分かります。

そんな便利な spine/Json.h。 しかし、spine/Json.h は決して使うべきではありません。 その理由は、以下の4つです。

  • リンクリストかつ線形探索なのでクソ遅い
  • 型の間違いを無視する
  • インターフェースが変わりやすい
  • キーの大文字小文字を無視する

この4つについて詳細に説明します。

なお、ここではコミットID 625b9501f320a08e6d3aff2ee2ad4e04c6872cc0 時点の spine/Json.hspine/Json.cpp について書いています。

リンクリストかつ線形探索なのでクソ遅い

この辺を見れば分かりますが、オブジェクトから要素を取ってくるために、リンクリストを線形探索しています。

これは配列の探索も同様です。n 番目の要素を取ってくるために、リンクリストを先頭から順番に辿っていく実装になっています。

これだけでもう、使うべきでないというのは確定です。 もちろん線形探索で問題のないケースというのも存在します。 しかし似たような選択肢が大量にある中、わざわざ遅い実装を使う理由なんてほぼありません。

なお、Cocos2d-x内には、spine/Json.h以外にも、最近ならrapidjson、古いのならJsonCppが入っているようです。 追加でライブラリを入れるのが面倒なのであれば、これらを利用するのがいいでしょう。

型の間違いを無視する

この辺を見れば分かります。 Json_getItemJson型の値を取り出し、それを整数として解釈しています。

これ自体は普通の処理です。 問題は、型に対するエラー処理が何も無いことです。

例えば、取り出した値の型が文字列だった場合には、何のエラーも出さず、単に 0 を返します。 null の場合も同様、何のエラーも出さずに、単に 0 を返します。 しかもこれは、引数のdefaultValueとは無関係に 0 を返します。 defaultValueを返すのは、オブジェクト型のデータからnameの要素が見つからなかった場合だけです。

Json_getStringの場合も同様に、エラー処理が何もありません。 値が文字列以外だった場合、nullptrが返されます。

Segmentation Faultで落ちないだけマシだと言えますが、最低でもassertぐらいは入れておくべきです。 可能なら例外を投げるなどの処理を入れた方がいいでしょう。

なお、spine/Json.hで型をちゃんと見た上で値を取り出すなら、以下のようになります。

Json* p;
if (p->type != Json_Object)
    throw "オブジェクト型じゃない";
Json* q = Json_getItem(p, "hoge");
if (q == nullptr)
    throw "hogeが見つからなかった";
if (q->type != Json_Number)
    throw "hogeの値が数値型じゃない";

// 無事目的の値を取得
int result = q->valueInt;

インターフェースが変わりやすい

以前はJson_getItemAtJson_getSizeという関数がありましたが、今はもう消されています

つまり配列から要素を取得したり、サイズを取得するには、このような実装を書いてあげる必要があるということです。 また、Json_getItemAtを使っていた人は、新しいCocos2d-xのバージョンに乗り換えた際に、この部分を修正する必要があります。

spine/Json.hはSpineのためのJSONライブラリなので、Spine側で使わない関数は消されるし、もしかしたらspine/Json.hが丸ごと消える可能性があります。 そのようなライブラリを使い続けるというのは、後のコストを考えるとかなり危険だと言えます。

キーの大文字小文字を無視する

比較の処理を見れば分かるように、tolower小文字に変換した上でキーを比較しています。

tolowerの第2引数を省略した場合、グローバルなロケールが使われます。 そして、ロケールによってtolowerの返す値は異なります。 ロケールによって結果が異なる例はcppreference.comのtolowerなどを見るといいでしょう。

このようなグローバルな値に依存するということはつまり、最初の検索で成功したのに、次の検索では失敗する可能性があるということです。 どこかのライブラリがふとした拍子にstd::setlocaleを呼び出しただけで、意図しない結果になることがあるのです。

このような比較をしているspine/Json.hは使うべきではないでしょう。

最後に

spine/Json.h を使うべきではない理由を4つ説明しました。 しかし実際のところ、これは spine/Json.h が悪いわけではありません。

そもそも spine/Json.h は、Spine で吐き出された JSON を読むために作られたライブラリです。 その JSON は、要素数が少ないため線形探索でも問題ありません。 自動で生成されたものを Spine の内部で読むだけだから、Spine を使うユーザにとっては型の間違いなんて関係ありません。 同様の理由で、大文字小文字を無視されても問題ありません。 配列のデータを取ってくる処理も必要ありません。

つまり spine/Json.h は、目的に対して十分な機能を有しています。 しかし、決して汎用的なJSONライブラリではありません。

spine/Json.hは利用せず、他のJSONライブラリを使うようにしましょう。