READMEは英語で書かれているので,日本語でyajl-dについて紹介する記事です.内容は執筆時点でのバージョンである0.2.0です.
yajl-d
yajl-dはyajlのD言語バインディングです.標準のstd.jsonはちょっと使う分には良いんですが,オブジェクトからのマッピングがなかったり,ストリーミング的にデコード出来なかったりと,いくつか欲しい機能がなかったので作りました.
インストール
まずyajl本体をインストール.大抵各OSにはパッケージがあると思います.Macならhomebrewを使えば楽です.
brew install yajl
yajl-dはyajlという名前でdubに登録してあるので,dub.jsonのdependencies
に追加すれば利用出来ます.以下は単純な例です.
"dependencies": {
"yajl": ">=0.1.2"
}
yajlを使うには以下のようにimportします.
import yajl;
API
encode(value) / decode(json)
単にJSONを扱うだけであれば,encode
/decode
のペアが楽です.encode
にはJSONにシリアライズしたいオブジェクトを,decode
にはJSON文字列を渡します.
また,decode
のtemplateパラメータには,JSONにマッピングしたい型を指定します.指定しなければ,std.json.JSONValue
が返ります.
import yajl;
struct Hoge
{
ulong id;
string word;
bool yes;
}
void main()
{
// json is {"id":100,"word":"hey!","yes":true}
string json = encode(Hoge(100, "hey!", true));
// Convert decoded result into Hoge
Hoge hoge = decode!Hoge(json);
assert(hoge.id == 100);
// The default return type is std.json.JSONValue
JSONValue jv = decode(json);
assert(jv["id"].integer == 100);
}
独自のJSON型を作らずにstd.json.JSONValue
を使ったのは,JSON型の実装としてstd.json.JSONValue
は基本的な機能を備えていたからです.
encode(value, opt) / decode(json, opt)
encode
/decode
の第二引数にオプションを渡すバージョンです.
- Encoder.Option
オプション群は以下のようになっています.詳細を説明するのが面倒なので,yajlの説明を参照してください.
static struct Option
{
bool beautify;
bool validateUTF8;
bool escapeSolidus;
string indentString;
}
- Decoder.Option
Encoder.Option
と同じく,詳細はyajlの説明を参照してください.missingHandler
はyajl-d独自のもので,後で説明します.
static struct Option
{
bool allowComments;
bool dontValidateStrings;
bool allowTrailingGarbage;
bool allowMultipleValues;
bool allowPartialValue;
MissingHandler missingHandler;
}
両方とも,Optionを生成して第二引数に渡すだけです.以下はdecodeの例です.
Decoder.Option opt;
opt.allowComments = true;
decode(json, opt);
Encode / Decoder
encode
/decode
は,中ではEncoder
,Decoder
を使っています.この二つを直接使えば,インスタンス生成を抑えてパフォーマンスが改善でき,ストリーミング的にデコードが出来たりします.
Encoder
はかなりシンプルな実装になっています.encode(value)
の中身は以下のようになっています.
Encoder encoder;
encoder.encode(value);
// Encoderを使い回すことで生成コストを抑えられる
encoder.encode(value2);
Decoder
は実装がそもそもStreaming Decoderになっていて,少しAPIがEncoder
とは違います.以下がdecode
の中身です.
Decoder decoder;
if (decoder.decode(json))
return decoder.decodedValue!T;
decode
ではデコード出来たかどうかが真偽値で返ってきます.false
だった場合には不完全なJSONが渡ってきたということで,次のdecode
呼び出しにさらにJSONを渡せます.ネットワーク上だと断続的にデータが渡ってきたりするので,そのような場合に効率的にデコード出来るようになってます.
その後,decodedValue
を呼び出すことで,デコード結果を取得出来ます.
Decoder
は内部に状態を持っていて,デフォルトでは複数JSONを連続してデコード出来なくなっています.インスタンスを使い回して効率的にデコードするには,以下のようにオプションを指定します.
Decoder.Option opt;
opt.allowMultipleValues = true;
Decoder decoder = Decoder(opt);
foreach (j; jsons) {
decoder.decode(j);
decoder.decodedValue;
}
JSONでキーが足りない時にコールバックする
Decoder.Option
のmissingHandler
をセットすると,受け取ったJSONを構造体に変換する時にキーが見つからなければ,そのキーを引数にコールバックが呼ばれます.
struct Test
{
string name;
int age;
}
void main()
{
auto encoded = `{ "name": "Bob", "age": 20}`;
auto missing = `{ "name": "Bob"}`;
Decoder.Option opt;
opt.missingHandler = (string field) { writeln(field); };
writeln(decode!Test(encoded, opt));
writeln(decode!Test(missing, opt)); // Callback called with missing field
}
D言語のキーワードを持つJSONを扱う
今までの例を見れば分かるように,yajl-dは構造体やクラスをエンコード/デコードするときに,各メンバの名前を自動的にキーにします.しかし,D言語は予約語は変数名に出来ないので,そのようなJSONはデフォルトでは上手く扱えません.
このケースに対応するには,JSONName
アノテーションを使います.以下のように対象のメンバに対してJSONName
を使い,マッピング先を指定出来ます.
struct Hoge
{
ulong id;
@JSONName("body") string _body;
bool yes;
}
// {"id":100,"body":"hey!","yes":true}
string json = encode(Hoge(100, "hey!", true));
この機能はAdamさんから頂いたパッチで実現しました.
パフォーマンス
手元のMBPでstd.json,vibe.data.json,yajlを使って,構造体をJSON文字列へのエンコード,JSON文字列をJSON型へデコードするのを簡単に比較してみた所,とりあえず一番速かった(ソースはココ).
encode:
json: 229240 QPS
vibe: 416995 QPS
yajl: 624793 QPS
decode:
json: 230197 QPS
vibe: 271000 QPS
yajl: 440328 QPS
Encoder
/Decoder
を使って複数JSONを処理すれば,もっとパフォーマンスが出ます.これに関してはREADMEのパフォーマンスの所を見てください.
まとめ
yajl-d,yajl依存があるのでstd.jsonほど手軽ではないですが,色々と追加機能もありパフォーマンスもいいので,何かD言語でJSONを扱うことがあれば,検討してみてください :)