Posted at

C++11でjsonを扱う方法。json11

More than 3 years have passed since last update.


json11とは

C++11で書かれたjsonライブラリで

使い方が綺麗なため、好まれて使われていると思います

私は、以前はpicojsonを使ってましたが最近はjson11を使ってます

https://github.com/dropbox/json11/blob/master

ここから json11.cppとjson11.hppをダウンロードして使ってます


使い方

https://github.com/dropbox/json11/blob/master/test.cpp

にサンプルが書いているので読めばわかりますが、補足します


jsonパース

サンプルのように


parse


const string simple_test =
R"({"k1":"v1", "k2":42, "k3":["a",123,true,false,null]})";

string err;
auto json = json11::Json::parse(simple_test, err);


とすれば、簡単に文字列から jsonにパース出来ます。

json.dump() とすると、jsonの文字列が取得出来ます

パースエラーがあれば errにエラーメッセージが返ってきます

jsonは シングルクオートではダメなので、ダブルクオートのエスケープが面倒ですが

そんな時 R"()" をつかって ヒアドキュメントにすると 上記のように楽です

見てわかるとおり "k3":["a",123,true,false,null] これで配列要素を扱うことが出来ます


値の取得

サンプルのように


get

    cout << "k1=" << json["k1"].string_value() << endl;

cout << "k2=" << json["k2"].int_value() << endl;

for (auto &k : json["k3"].array_items()) {
cout << " - " << k.dump() << endl;
}


これで値を取得できますね。

string_value で文字列の値を

int_value で整数の値を

array_items で配列を取得します

他にも bool_value、number_value があります

ただし 64bit intの値は取得できないので

大きい整数は、number_valueで取得します。ただし 仮数部の52ビットまでしか扱えません

それ以上の値をとるには、stringで送るのが良いかもしれません


json構築

サンプルのように


literals

    Json obj = Json::object({

{ "k1", "v1" },
{ "k2", 42.0 },
{ "k3", Json::array({ "a", 123.0, true, false, nullptr }) },
});

と、イニシャライズリストを使い、jsonを構築できます

とても綺麗な実装です。この綺麗さが json11の人気のポイントだと思います

配列はJson::array() で構築します

文字列で与えるのに比べ、コンパイル時に解決出来るので、おそらくこの方が早くなります

また、STLコンテナを使い、構築する事が可能です!


stlarray

    std::list<int> l1 { 1, 2, 3 };

std::vector<int> l2 { 1, 2, 3 };
std::set<int> l3 { 1, 2, 3 };
std::array<int,3> l4 { 1, 2, 3 };

cout << json11::Json(l1).dump() << endl;
cout << json11::Json(l2).dump() << endl;
cout << json11::Json(l3).dump() << endl;
cout << json11::Json(l4).dump() << endl;


これらはいずれも、dump()すると

[1,2,3]

という 配列になります


stlmap

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };

std::multimap<string, string> m2 { { "k1", "v1" }, { "k2", "v2" } };
std::unordered_map<string, string> m3 { { "k1", "v1" }, { "k2", "v2" } };
std::unordered_multimap<string, string> m4 { { "k1", "v1" }, { "k2", "v2" } };

cout << json11::Json(m1).dump() << endl;
cout << json11::Json(m2).dump() << endl;
cout << json11::Json(m3).dump() << endl;
cout << json11::Json(m4).dump() << endl;


stl::map系を使うと、名前付きフィールドを構築できます

上記を実行するといずれも

{"k1": "v1", "k2": "v2"}

というjsonになります

とうぜん これらは複合させることが可能です


complex

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };

std::list<int> l1 { 1, 2, 3 };
json11::Json json1 = json11::Json::object {
{ "key1", m1 },
{ "key2", l1 }
};
cout << json1.dump() << endl;

とすると

{"key1": {"k1": "v1", "k2": "v2"}, "key2": [1, 2, 3]}

が得られます。

もちろんjson同士を複合させられます


comp

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };

std::list<int> l1 { 1, 2, 3 };

json11::Json json1 = json11::Json::object {
{ "key1", m1 },
{ "key2", l1 }
};

json11::Json json2 = json11::Json::object {
{ "json1", json1 },
{ "json2", "value" }
};

cout << json2.dump() << endl;


結果は

{"json1": {"key1": {"k1": "v1", "k2": "v2"}, "key2": [1, 2, 3]}, "json2": "value"}

json同士も連結できます


同じ階層で連結させたい・・

上記だと

{"key1": {"k1": "v1", "k2": "v2"}, "key2": [1, 2, 3]}

{"json1": {"key1": {"k1": "v1", "k2": "v2"}, "key2": [1, 2, 3]}, "json2": "value"}

連結した際に、1階層はいってしまいます。フラットに連結できないか?

{"k1": "v1", "k2": "v2", "key2": [1, 2, 3]}

{"k1": "v1", "k2": "v2", "key2": [1, 2, 3], "json2": "value"}

このように。

ということで json11のコードを見ます


json11

        typedef std::vector<Json> array;

typedef std::map<std::string, Json> object;

// Constructors for the various types of JSON value.
Json() noexcept; // NUL
Json(std::nullptr_t) noexcept; // NUL
Json(double value); // NUMBER
Json(int value); // NUMBER
Json(bool value); // BOOL
Json(const std::string &value); // STRING
Json(std::string &&value); // STRING
Json(const char * value); // STRING
Json(const array &values); // ARRAY
Json(array &&values); // ARRAY
Json(const object &values); // OBJECT
Json(object &&values); // OBJECT

// Implicit constructor: anything with a to_json() function.
template <class T, class = decltype(&T::to_json)>
Json(const T & t) : Json(t.to_json()) {}

// Implicit constructor: map-like objects (std::map, std::unordered_map, etc)
template <class M, typename std::enable_if<
std::is_constructible<std::string, typename M::key_type>::value
&& std::is_constructible<Json, typename M::mapped_type>::value,
int>::type = 0>
Json(const M & m) : Json(object(m.begin(), m.end())) {}

// Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc)
template <class V, typename std::enable_if<
std::is_constructible<Json, typename V::value_type>::value,
int>::type = 0>
Json(const V & v) : Json(array(v.begin(), v.end())) {}


このように Json::objectは mapのtypedefであり

コンストラクタにて mapとして受け取っているため

イニシャライザリストにて、フラットに連結するのは 少し難しそうです

アクセッサも


json11

    Json::Type Json::type()                           const { return m_ptr->type();         }

double Json::number_value() const { return m_ptr->number_value(); }
int Json::int_value() const { return m_ptr->int_value(); }
bool Json::bool_value() const { return m_ptr->bool_value(); }
const string & Json::string_value() const { return m_ptr->string_value(); }
const vector<Json> & Json::array_items() const { return m_ptr->array_items(); }
const map<string, Json> & Json::object_items() const { return m_ptr->object_items(); }
const Json & Json::operator[] (size_t i) const { return (*m_ptr)[i]; }
const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; }

double JsonValue::number_value() const { return 0; }
int JsonValue::int_value() const { return 0; }
bool JsonValue::bool_value() const { return false; }
const string & JsonValue::string_value() const { return statics().empty_string; }
const vector<Json> & JsonValue::array_items() const { return statics().empty_vector; }
const map<string, Json> & JsonValue::object_items() const { return statics().empty_map; }
const Json & JsonValue::operator[] (size_t) const { return static_null(); }
const Json & JsonValue::operator[] (const string &) const { return static_null(); }

const Json & JsonObject::operator[] (const string &key) const {
auto iter = m_value.find(key);
return (iter == m_value.end()) ? static_null() : iter->second;
}
const Json & JsonArray::operator[] (size_t i) const {
if (i >= m_value.size()) return static_null();
else return m_value[i];
}


と、頑なに constなので、難しそうです

(非constメンバを作り 内部のm_ptrにアクセス出来るようにすれば、jsonノードを任意でいじれるようになるかもしれません)

とりあえずは、方針として

Json::object(map) を作成し insertていく

これでやってみようと思います


map

    std::map<string, string> m1 { { "k1", "v1" }, { "k2", "v2" } };

std::list<int> l1 { 1, 2, 3 };

json11::Json::object jo(m1.begin(),m1.end());
jo.insert(pair<string,json11::Json>("key2", l1));
cout << json1.dump() << endl;


{"k1": "v1", "k2": "v2", "key2": [1, 2, 3]}

が出力されました。

このように Json::objectを直接操作すれば、任意のjsonを作る事が出来そうです。


まとめ

json11は綺麗だが、jsonの生成に関しては今のところ使いやすい印象がない。

特に 任意のノードにノードを連結しようと思うと不便を感じる

結局は mapを直接触る事になるんだろうか?

詳しい方、ヘルプ!