picojsonとv8::Valueの変換

nodeのアドオンを作ってjavascript側とデータをやりとりしようとすると、
v8のデータクラスを扱うことになるのですがこれが結構分かりづらいのですね。

たとえば、意味のある例ではないのですが
javascriptのオブジェクトを受け取り、その中の"data1"要素である配列に100.0というデータを追加した配列を新たなオブジェクトとして返す」
というメソッドをC++のアドオンで実装してみます。

エラー処理は省略して、以下のようになるでしょうか。(v8に慣れていないので間違っていたらすみません)

v8::Handle<v8::Value> ary_push(const v8::Arguments& args)
{
        v8::HandleScope scope;

        // 引数のv8::Valueをv8::Objectにキャスト
        v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(args[0]);
        // "data1"要素(Array)を取得
        v8::Handle<v8::Array> data1 = v8::Handle<v8::Array>::Cast(obj->Get(v8::String::New("data1")));
        // 配列を新たなオブジェクトとしてコピー
        v8::Handle<v8::Array> ary = v8::Handle<v8::Array>::Cast(data1->Clone());
        // 最後に100.0を追加
        data1->Set(ary->Length(), v8::Number::New(100.0));

        // javascriptに返す
        return scope.Close(ary);
}

using namespace v8; すると多少はマシかもしれませんが、
ぱっと見てすっと頭に入るコードとは言えないです。少なくとも私にとっては。

ここで登場していただくのが picojson というC++JSONを扱う軽量ライブラリです。
ヘッダのみで実装されています。

これを少し改造して、上記の内容をなるべく簡単に直感的に表現できるようなものを作ってみました。
改造したpicojsonのソースはこちらに置いてあります。

まずpicojsonのオブジェクトとv8のオブジェクトを変換する、以下のメソッドを追加しました。

namespace picojson {
class value {
    value(v8::Handle<v8::Value> v8val); // v8->picojson コンストラクタ
    v8::Handle<v8::Value> to_v8() const; // picojson->v8 オブジェクト生成メソッド
};
}

また、シンタックスシュガー的な以下のようなメソッドもコテコテと追加してます。
(pull requestしましたが方針と合わないのでrejectということでした ^^;)

namespace picojson {
class value {
    // キャストオペレータ
    operator bool&();
    operator double&();
    operator std::string&();
    operator array&();
    operator object&();
    operator const bool&() const;
    operator const double&() const;
    operator const std::string&() const;
    operator const array&() const;
    operator const object&() const;

    // array, object用の operator[]
    value& operator[](int idx);
    value& operator[](size_t idx);
    value& operator[](const std::string& key);
    value& operator[](const char* key);

    // array, objectの要素追加
    template <typename T> void insert(const std::string& key, T val);
    template <typename T> void push(T val);

    // empty()
    bool empty() const;
};
}

これを使い、v8オブジェクトを一度picojsonに変換して操作し、最後にv8オブジェクトにして戻すという流れで書き直してみたのが以下のコードです。

v8::Handle<v8::Value> ary_push2(const v8::Arguments& args)
{
        v8::HandleScope scope;

        // 引数のv8オブジェクトからpicojsonのオブジェクトを生成
        picojson::value obj(args[0]);
        // "data1"要素(Array)を取得
        picojson::value data1 = obj["data1"];
        // 最後に100.0を追加
        data1.push(100.0);

        // v8に変換してjavascriptに返す
        return scope.Close(data1.to_v8());
}

最初のv8オブジェクトを直接操作するコードに比べるとかなり直感的に理解しやすいコードになっています。
まあこの例が「新たなオブジェクトとして返す」というかなり恣意的なものなのはお気づきかとは思いますが...

リファレンスで渡されたjavascriptのオブジェクトを直接操作することができないので万能ではないです
(だからこそこのような例になってしまうわけなのです)が、
複雑なオブジェクトを生成して返す、というようなメソッドは結構すっきり書けるので使いどころはあるのではないかと思います。