LoginSignup
10
9

More than 5 years have passed since last update.

Msgpack in C++で自由なデータ型のmap形式を抽出する

Posted at

背景

msgpackはJSONのように自由なデータ形式を使えつつ、直接の可読性を捨てることでデータ容量を節約し、実装的にもserialize, deserializeを高速化している今どきな感じのシリアライズフォーマットです。KVSに構造化データをがしがし入れていくためデータのシリアライズが必要でしたが、ありきたりなJSONよりはmsgpackも使ってみるか、ぐらいの気持ちで使い始めてみました。

やりたいこと

JSONで例えると、こういうデータを扱いたいと思いました。

{
    "ts": 13345133.43,
    "src": "10.0.0.1",
    "domain": {
            "example.com": 1,
            "example.org": 2,
            "example.net": 3,
    }
}

特徴としては、

  1. マップ形式だが、バリュー部分に数値や文字列などのデータ形式が入り交じっている
  2. 出現するキーがメッセージごとにまちまちで統一されない

となります。1だけ解決したいならユーザ定義Classへのバインド機能 (MSGPACK_DEFINE) を使う、2だけならmapではなくarray形式をうまく使うようにする、で概ね解決できます。実際、1と2を組み合わせてやればある程度は解決できる可能性もあるのですが、実装上の制約によって本来あるべきと考えるデータ構造の形式をねじまげるのはなんか癪な気持ちになったので、いろいろ調べてみました。ちなみに上記のようなデータをシリアライズする方法はQuickStartガイドに示されています。しかし、デシリアライズする方法が無い…。

解決方法

調べ方の問題かもしれませんが、見つかったのは構造体内のデータに直接触るやり方でした。参考にしたデータは本家のソースコードの object.cpp の64-76行目で、表示可能な文字列に変換してostreamに流し込んでいるところ。

    case type::MAP: // map形式のオブジェクトだったら
            s << "{";  // まずはカッコを表示
            if(o.via.map.size != 0) { // マップ内のサイズが0じゃなかったら、
                    object_kv* p(o.via.map.ptr); // object_kv構造体をobject構造体の .via.map.ptr から作成
                    s << p->key << "=>" << p->val; // 最初のキー+バリューを表示、
                    ++p; // 一つずらす
                    for(object_kv* const pend(o.via.map.ptr + o.via.map.size);
                                    p < pend; ++p) { // 終点を設定して、そのままポインタをずらしつづける
                            s << ", " << p->key << "=>" << p->val; // キーとバリューを表示
                    }
            }
            s << "}"; // 締め
            break;

これを参考にunpackするコードを書こうとすると、こんな感じ。dataがシリアライズされたデータへのポインタ、lenがデータ長とし、nameというキーの文字列データがバリューで入っているものとします。

    msgpack::unpacked msg;
    msgpack::unpack(&msg, data, len);  // デシリアライズ
    msgpack::object obj = msg.get ();

    if(obj.via.map.size != 0) {
  msgpack::object_kv* p(obj.via.map.ptr);
  for(msgpack::object_kv* const pend(obj.via.map.ptr + obj.via.map.size);
      p < pend; ++p) {
    std::string key, name;
    p->key.convert (&key);

    if (k == "name") {
      p->val.convert (&name);
          std::cout << k << "=>" << name << std::endl;
        }
      }
    }

p->keyp->valueはそれぞれmsgpack::object型で数値、真偽値、文字列をラップするような形式になっており、適宜別のデータ型に変換できるようです。

一応、これでやりたいことはできたのですが、なんか外法を使っているような気になります。オブジェクト指向なのに構造体の内部データに直接触ってしまうだけでいろいろと罪 悪感がつきません。なんかもっといい方法を知っている人がいたら、ぜひご教授ください。

参考ページ

過去の自分の記事より転載

10
9
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
9