2015年2月21日土曜日

JSONデータのハンドリング:c++

さてこれまでブラウザ上でJSONデータの扱いを調べてきましたが、これらは入力データの編集用と思って調べてきました。こっからが本題のc++によるJSONデータの扱いです。

もともと、本職はc++プログラムがほとんどなんで、いつも入出力のGUIに悩まされていました。それをCouchDBで楽したいというところで、色々調べていましたがだいたい目処がついてきたので、さてそれをc++プログラムでどうやってハンドリングしたらいいのか?というところまできました。

c++でJSONのパーサはいくつか見つかりましたが、picojsonがヘッダファイル一つというシンプルさでなかなか秀逸です。早速これを試してみました。

まず試験データとしてのJSONファイル(test3.json)です。

{"message":"success",
  "number":[1, 2],
  "person":{"name":"hoge", "age":38},
  "follow":[
 {"name":"hoge", "age":38},
 {"name":"huga", "age":42}
  ],
  "returnCode":0}

わざと複雑な階層構造を持たせています。このファイル名を引数として動くサンプルが以下です。

#include 
#include 
#include "picojson.h"

int main(int argc, char** argv)
{
  picojson::value v;
  std::ifstream infile;
  std::string inStr = "";

  infile.open(argv[1]);
  while (!infile.eof()) {
    std::string work = "";
    infile >> work;
    inStr += work;
  }
  infile.close();

  std::string err = parse(v, inStr);

  if (!err.empty()) {
    std::cerr << picojson::get_last_error() << std::endl;
    return 1;
  }
  
  // dump json object
  std::cout << "---- dump input ----" << std::endl;
  std::cout << v << std::endl;

  // accessors
  std::cout << "---- analyzing input ----" << std::endl;
  if (v.is()) {
    std::cout << "input is an object" << std::endl;
    //const picojson::object& o = v.get();
    picojson::object& o = v.get();
    for (picojson::object::const_iterator i = o.begin(); i != o.end(); ++i) {
      std::cout << i->first << "  " << i->second << std::endl;
    }
    
    std::cout << std::endl;

    // 一番外側のobjectの取得
    std::string& s1 = o["message"].get();
    //picojson::array& s1 = o["number"].get();
    std::cout << s1 << std::endl;

    std::cout << std::endl;

    // 値の変更
    o["message"] = (picojson::value)std::string("fail");

    // データの追加
    o.insert(std::map::value_type("piyopiyo", 10.0));

    for (picojson::object::const_iterator i = o.begin(); i != o.end(); ++i) {
      std::cout << i->first << "  " << i->second << std::endl;
    }
    
    std::cout << std::endl;

    // 内部のobject値の取得
    picojson::object& o1 = o["person"].get();
    std::cout << o1["name"].get() << " " << o1["age"].get() << std::endl;

    std::cout << std::endl;

    // array値の取得
    picojson::array& a1 = o["follow"].get();
    o1 = a1[1].get();
    std::cout << o1["name"].get() << " " << o1["age"].get() << std::endl;

    std::cout << std::endl;

    // serialize(JSON形式の文字列に戻す。JavaScriptのstringify()と同じ。)
    std::string str = picojson::value(o).serialize();
    std::cout << str << std::endl;

  }
  
  return 0;
}

実行すると以下の様な出力をだしてきます。

---- dump input ----
{"follow":[{"age":38,"name":"hoge"},{"age":42,"name":"huga"}],"message":"success","nu\
mber":[1,2],"person":{"age":38,"name":"hoge"},"returnCode":0}
---- analyzing input ----
input is an object
follow  [{"age":38,"name":"hoge"},{"age":42,"name":"huga"}]
message  "success"
number  [1,2]
person  {"age":38,"name":"hoge"}
returnCode  0

success

follow  [{"age":38,"name":"hoge"},{"age":42,"name":"huga"}]
message  "fail"
number  [1,2]
person  {"age":38,"name":"hoge"}
piyopiyo  10
returnCode  0

hoge 38

huga 42

{"follow":[{"age":38,"name":"hoge"},{"age":42,"name":"huga"}],"message":"fail","numbe\
r":[1,2],"person":{"age":42,"name":"huga"},"piyopiyo":10,"returnCode":0}

うまく動いていますね。階層構造の扱いも、値の変更も思い通りになります。
ちょっと面倒くさいのが、いちいちpicojson::value形式で読みこんだ後、データの操作のためにpicojson::object形式にしないといけないこと。JavaScriptでも同じような考え方でしたが何でですかね?picojsonの実装はヘッダファイル一つなので、少し読んでみると、picojson::valueではvector形式で読み込んでいるようですが、すぐにmap形式にしてしまいます。確かにこれならアクセスするのが簡単で、処理速度も早いんですがデータ項目の順番がkeyでソートされてしまい順番が変わってしまいます。JSONとしては問題ないんですが、デバッグ時に少し悩みそうです。

後、このサンプルではparse()関数を使っていますが、picojson::valueは、>>を使って直接読み込むことができます。わざわざサンプル用に、stringでファイルを読み込み、parse()関数を使うように変更しました。こっちの方がエラーの扱いがわかりやすいからなんですがね。

picojsonなかなかいいんですが、一つ難点を言えば、c++のtemplate機能を使い倒しているため、エラーが出てくると、わけのわからないエラーメッセージを出してくるのがつらいです。c++の機能なんで、しかたないんですが。


0 件のコメント:

コメントを投稿