シリアライゼーションの話


[D言語 Advent Calendar 2012](http://qiita.com/advent-calendar/2012/d)の14日目の記事です．D言語でのシリアライゼーション(マーシャルの方が良い気がしますが，こっちで統一します)について書きます．

D言語はRubyなどと違って組み込みのシリアライゼーションが提供されておらず，ライブラリとしていくつかシリアライゼーションライブラリが提供されています．
それらを紹介しつつ，違いを簡単に比較してみることにします．

## [std.json](https://github.com/D-Programming-Language/phobos/blob/master/std/json.d)

今Webでもっとも広まっているであろうフォーマット[JSON](http://www.json.org/)を扱う標準ライブラリです．[JSONValue構造体](https://github.com/D-Programming-Language/phobos/blob/master/std/json.d#L50)があるので，これでJSONを扱います．

```d
import std.json;

JSONValue j = parseJSON(`{"a":1,"b":"hoge"}`);
assert(j["a"].integer == 1);
assert(j["b"].str == "foo");
assert(toJSON(&j) == `{"a":1,"b":"foo"}`);
```

今の所D言語のプリミティブな型とJSONValueを一発で相互に変換する機能は提供されていないので，その辺は手で書く必要があります．
俺はArangoDBドライバでは[それ用の関数を定義しています](https://github.com/repeatedly/reed/blob/master/src/reed/util.d#L48)．また，Phobosに同じような機能が[提案されてたりします](https://github.com/D-Programming-Language/phobos/pull/885)．

### 注意点

[Robertのjsonモジュール](https://jshare.johnshopkins.edu/rjacque2/public_html/json2.mht)で置換される話がずっと前から出ています．std.variantベースの実装になっており，パッチが結構変更が激しいのもあってかまだ置換されていません．

## [std.xml](https://github.com/D-Programming-Language/phobos/blob/master/std/xml.d)

今でも場所によってはバリバリ使われているXMLを扱うライブラリです．で，紹介しょうかと思ったんですが，

> This module is considered out-dated and not up to Phobos' current standards. It will remain until we have a suitable replacement, but be aware that it will not remain long term.

と公式のドキュメントに書かれるようなモジュールなのであえて説明しません！

新しい実装の話はチラホラ出ているんですが，筆頭だったTangoのXMLモジュールがライセンス周りで駄目になった後は，いくつか候補はあるものの決定的なモジュールが出てきてません．

## [msgpack-d](https://github.com/msgpack/msgpack-d)

[MessagePack](http://msgpack.org/)という多言語間でやり取り可能なバイナリフォーマットのライブラリです．作者は俺です．

使い方は基本的に以下のようにpackでシリアライズ，unpackでデシリアライズになります．tupleofなどを使っているので，既存の構造体でもちゃんとシリアライズ出来ますし，[toMsgpack/fromMsgpackを定義すれば](https://github.com/msgpack/msgpack-d/blob/master/example/custom.d#L97)，ルーチンをカスタマイズ出来ます．

```d
import msgpack;

struct Person
{
    string name;
    uint   age;
}

// 裏ではPacker/Unpackerを使っている
Person handa = Person("shinobu", 16), other;
unpack(pack(handa), other);
assert(other.name == handa.name);
assert(other.age == handa.age);
```

また，ネットワークなどを考慮し断続的にデシリアライズするためのStreamingUnpackerや，遅延して型を変換するためのValue型も提供しています．
興味のあるかたはexampleディレクトリのコードを参照してください．

MessagePack-RPCも実装しようとは思っているんですが，速度を考えるとライトウェイトな並行処理ライブラリが先に必要になるのと，今のところ個人的に必要になってないので，ちょっと迷っている所です．

### 注意点1: real型

D言語のreal型もシリアライズ出来るようになってます．ただ，このreal型はMessagePackの標準フォーマットではありません．これを使うと多言語間で交換出来なくなるので注意してください．

### 注意点2: 循環参照

MessagePackそのものは循環参照をサポートしてません．実際はあまり問題にはならないのですが，Andrejは必要になったようで，彼は独自に循環参照サポートをmsgpack-dに[実装してます](https://github.com/AndrejMitrovic/msgpack-d/tree/ClassRefs)．
もし必要になるのであれば，多言語間での交換はできなくなりますが，こちらを使ってください．

## [Orange](https://github.com/jacob-carlborg/orange)

これはBoost.Serializationのような独自ライブラリです．シリアライズ/デシリアライズのAPIを提供するSerializerクラスと，データの保存形式を決めるArchiveの二つからなります．

```d
import orange.core._;
import orange.serialization._;
import orange.serialization.archives._;

auto naito = Person("momoko", 16);
auto archive = new XmlArchive!(char);
auto serializer = new Serializer(archive);

serializer.serialize(naito);
auto other = serializer.deserialize!(Person)(archive.untypedData);

assert(other.name == naito.name);
```

Archiveはプラガブルになっているんですが，今の所XML形式しかサポートしてません．

循環参照を含むオブジェクトグラフの保存，型を登録することによるスーパークラスを経由してのサブクラスのシリアライズ，XMLのSAXのようなイベントハンドラの登録などかなり機能は揃っています．

std.serializationとして標準ライブラリに含めようという流れが，一部ではあります．

### 注意点

現在最もリッチな機能を提供しているシリアライゼーションライブラリですが，速度にかなり問題を抱えています．

[Performance issue](https://github.com/jacob-carlborg/orange/issues/16)

ここに書かれてますが，msgpack-dだと6.3msで終わるシリアライズの処理が31s(ミリ秒ではなく秒)も掛かっています．上のパフォーマンス比較を行ったAndrejがOrangeを諦めた最も大きな要因はこの遅さです．

残念ながらまだ改善はされていません．

## [Thrift](http://thrift.apache.org/)

Facebookが開発した多言語間通信フレームワークで今はApacheプロジェクトです．メインはRPCですが，シリアライゼーションも勝手にしてくれます．
Thriftを使うには，IDLという定義ファイルが別途必要です．IDLを元に様々なコードを生成し，シリアライゼーションと通信を行います．なので，実際は他に紹介しているライブラリとは違いシリアライゼーション単体で使うことはありません．

```d
struct UserProfile
{
    1: i32 uid,
    2: string name,
    3: string blurb
}

service UserStorage
{
    void store(1: UserProfile user),
    UserProfile retrieve(1: i32 uid)
}      
```

上記のコードは公式サイトに載っているIDLの例で，これをthriftコマンドに食わせると対応したD言語のコードが出力されます．サーバであれば後はstoreなどのメンバ関数の中身を埋め，クライアントであれば```client.retrieve(10)```のように呼ぶだけになります．

Thriftの最新版である0.9.0には，既にD言語実装が同梱されています．

### 注意点

Thriftは循環参照をサポートしてないなど，MessagePackと同じような問題を持っています．

## [bson.d](https://github.com/repeatedly/Mongo-D-Driver/blob/master/src/mongo/bson.d)

昔MongoDBを使っていた時に実装した[BSON](http://bsonspec.org/)モジュールです．BSONを実装して力尽き，MongoDBクライアントの実装までは行きませんでした．今メンテナンスしてないのであれですが，immutableベースの設計になっていて，最新に追従すればBSONライブラリとしては使えます．ま，とは言ってもBSONを使うのってMongoDBしかないので，全然汎用性はないですね…

## まとめ

std.jsonやstd.xmlは必要であれば使ってくださいという感じです．

これらを除くと，循環参照をサポートしているなど，他の言語組み込みと同等の機能を今提供出来そうなのはOrangeくらいです．ただ，速度が遅くシリアライズ後のデータサイズが大きいなど，いくつか実用上の問題を抱えています．

オブジェクトグラフの再現が必要ないのであれば，msgpack-dが最有力です．実際Andrej以外にも何人か利用者がいます．また，FluentdのようにTCP/HTTPの上でMessagePackを受け付けるやつや，ZeroMQと組み合わせているプロジェクトもあるので，MessagePack-RPC以外でも多言語間通信でそれなりに使われています．

RPC前提のシステムであれば，Thriftが使えます．Thrift APIを提供しているOSSのプロジェクトはいくつかあるので，提供されているIDLを食わせればD言語からでもアクセス出来ます．

以上，簡単にですが今使えるシリアライゼーションライブラリを見てきました．MessagePackとThriftがあるので，他の言語とやり取りするのは基本的に困らないと思っています．
将来的にstd.concurrencyがネットワーク越しのやり取りをするのであれば，オブジェクトグラフを保存出来るシリアライゼーションが必須になります．しかし今のOrangeでは絶望的にパフォーマンスが駄目なので，この辺はPhobosの今後の課題でもあります．

次15日目は[@k3_kaimu](http://qiita.com/users/k3_kaimu)さんです！