yajl-dの使い方


READMEは英語で書かれているので，日本語で[yajl-d](https://github.com/repeatedly/yajl-d)について紹介する記事です．内容は執筆時点でのバージョンである0.2.0です．

## yajl-d

yajl-dは[yajl](http://lloyd.github.io/yajl/)のD言語バインディングです．標準のstd.jsonはちょっと使う分には良いんですが，オブジェクトからのマッピングがなかったり，ストリーミング的にデコード出来なかったりと，いくつか欲しい機能がなかったので作りました．

## インストール

まずyajl本体をインストール．大抵各OSにはパッケージがあると思います．Macならhomebrewを使えば楽です．

```sh
brew install yajl
```

yajl-dはyajlという名前でdubに登録してあるので，dub.jsonの`dependencies`に追加すれば利用出来ます．以下は単純な例です．

```json
  "dependencies": {
    "yajl": ">=0.1.2"
  }
```

yajlを使うには以下のようにimportします．

```d
import yajl;
```

## API

### encode(value) / decode(json)

単にJSONを扱うだけであれば，`encode`/`decode`のペアが楽です．`encode`にはJSONにシリアライズしたいオブジェクトを，`decode`にはJSON文字列を渡します．
また，`decode`のtemplateパラメータには，JSONにマッピングしたい型を指定します．指定しなければ，`std.json.JSONValue`が返ります．

```d
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の説明](http://lloyd.github.io/yajl/yajl-2.1.0/yajl__gen_8h.html#a57c29080044a7231ac0cf1fead4de4b0)を参照してください．


```d
static struct Option
{
    bool beautify;
    bool validateUTF8;
    bool escapeSolidus;
    string indentString;
}

```

- Decoder.Option

`Encoder.Option`と同じく，詳細は[yajlの説明](http://lloyd.github.io/yajl/yajl-2.1.0/yajl__parse_8h.html#a5434a7c3b3165d782ea42c17d6ba9ac3)を参照してください．`missingHandler`はyajl-d独自のもので，後で説明します．

```d
static struct Option
{
    bool allowComments;
    bool dontValidateStrings;
    bool allowTrailingGarbage;
    bool allowMultipleValues;
    bool allowPartialValue;

    MissingHandler missingHandler;
}
```

両方とも，Optionを生成して第二引数に渡すだけです．以下はdecodeの例です．

```d
Decoder.Option opt;
opt.allowComments = true;
decode(json, opt);
```

### Encode / Decoder

`encode`/`decode`は，中では`Encoder`，`Decoder`を使っています．この二つを直接使えば，インスタンス生成を抑えてパフォーマンスが改善でき，ストリーミング的にデコードが出来たりします．

`Encoder`はかなりシンプルな実装になっています．`encode(value)`の中身は以下のようになっています．

```d
Encoder encoder;
encoder.encode(value);

// Encoderを使い回すことで生成コストを抑えられる
encoder.encode(value2);
```

`Decoder`は実装がそもそもStreaming Decoderになっていて，少しAPIが`Encoder`とは違います．以下が`decode`の中身です．

```d
Decoder decoder;
if (decoder.decode(json))
    return decoder.decodedValue!T;
```

`decode`ではデコード出来たかどうかが真偽値で返ってきます．`false`だった場合には不完全なJSONが渡ってきたということで，次の`decode`呼び出しにさらにJSONを渡せます．ネットワーク上だと断続的にデータが渡ってきたりするので，そのような場合に効率的にデコード出来るようになってます．
その後，`decodedValue`を呼び出すことで，デコード結果を取得出来ます．

`Decoder`は内部に状態を持っていて，デフォルトでは複数JSONを連続してデコード出来なくなっています．インスタンスを使い回して効率的にデコードするには，以下のようにオプションを指定します．

```d
Decoder.Option opt;
opt.allowMultipleValues = true;
Decoder decoder = Decoder(opt);
foreach (j; jsons) {
    decoder.decode(j);
    decoder.decodedValue;
}
```

### JSONでキーが足りない時にコールバックする

`Decoder.Option`の`missingHandler`をセットすると，受け取ったJSONを構造体に変換する時にキーが見つからなければ，そのキーを引数にコールバックが呼ばれます．

```d
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`を使い，マッピング先を指定出来ます．

```d
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型へデコードするのを簡単に比較してみた所，とりあえず一番速かった(ソースは[ココ](https://gist.github.com/repeatedly/c4a0352c3ec322446e49))．

```sh
encode:
json: 229240 QPS
vibe: 416995 QPS
yajl: 624793 QPS
decode:
json: 230197 QPS
vibe: 271000 QPS
yajl: 440328 QPS
```

`Encoder`/`Decoder`を使って複数JSONを処理すれば，もっとパフォーマンスが出ます．これに関しては[READMEのパフォーマンス](https://github.com/repeatedly/yajl-d#perfomance)の所を見てください．

## まとめ

yajl-d，yajl依存があるのでstd.jsonほど手軽ではないですが，色々と追加機能もありパフォーマンスもいいので，何かD言語でJSONを扱うことがあれば，検討してみてください :)