C++でJSONを扱う第4の方法 #8

公開日: 2012-01-17


JSONをC++で扱うのはなかなか難しい。JSONはJavaScriptのような動的な型付けの言語だと簡潔に扱えるが、C++のような静的な型付けの言語との相性はいまいちである。JavaScript だとこんな感じにできることが:

>> d = JSON.parse("{'foo': 123, 'bar': 'hello'}")
Object
>> d["foo"]
123
>> d["bar"]
"hello"

C++ でJSONを扱うと大体こんな感じのコードになると思う。

Dictionary* d = JSONReader::Read("{'foo': 123, 'bar': 'hello'}");
int n = d->GetInteger("foo");
std::string s = d->GetString("bar");

GetInteger(), GetString() など、型ごとに関数を定義しないといけないのがポイントだ。

これだとそんなに悪くない気もするが、GetInteger() でエラーが発生したらどうすればいいのだろうか。例外を飛ばすのはひとつのやり方だが、 Google C++ Style Guide [1] のように例外を禁止しているコードだと以下のようになるだろう(関数のオーバーロードも禁止されているので、GetInteger, GetString などの名前は残る)。

base::Dictionary* d = JSONReader::Read("{'foo': 123, 'bar': 'hello'}");
int n = 0;
if (!d->GetInteger("foo", &n))
 エラー処理
std::string s;
if (!d->GetString("bar", &s))
 エラー処理

d["foo"] のように1行でできたことが、3行になってしまった。フィールドにアクセスするたびに、いちいち3行書くのは面倒くさいし、コードは読みづらくなる。ついでに、同じフィールドにアクセスするたびに辞書を毎回ルックアップするのは効率が悪い。

じゃあどうするかというと、JSONのデータを構造体に移してから使おうという話になるのだが、このデータ取り出しコードは書くのが面倒くさい。GetString() やらをちまちま呼び出していけばいいだけなのだが、JSONデータが複雑で辞書の中に辞書があったりすると、コードもややこしくなる。単調なコードの割には、いかにもバグが入りやすそうだ。

この問題はどうやって解決したらいいのだろうか。

1) がんばって、ちまちま書き続ける(しんどいな)
2) スクリプトなどでコード生成をする(ビルドがややこしくなるのがやだな)
3) JSON to protocol buffers を C++ で書く(protocol buffers [2] へ依存が増えるのがやだな)

2) か 3) かなあと思っていたのだが、識者に相談したところ、そんなもの4 だろ!みたいな答えが返ってきた。4 って何よ?と思ったら、こういうことらしい:

4) JSON to struct を C++ のコード内で宣言的に書けるようにする

こんなのどうやって書くの?と聞いたら、ものすごい勢いで大まかなコードを書いてくれた。構造体のメンバーへのオフセットと、フィールド名の文字列を宣言的に結びつけるといのが基本的なアイディアらしい。彼曰く、自分で書く時間はないが、これで動くはず、とのこと。

本当にこんなの動くのかなあ?配列とか入れ子の辞書とかどうするの?と別の同僚に相談したところ、「おもしろそうですね。この線で行けそうですよ」ということで、書いてくれたのが JSONValueConverter だ:

http://src.chromium.org/viewvc/chrome/trunk/src/base/json/json_value_converter.h?view=markup

配列や入れ子の辞書も扱えるものが本当にできてしまった。しかも、私は一切手を動かさずに。。

[1] http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml
[2] http://code.google.com/p/protobuf/

Satoru Takabayashi