More than 1 year has 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を直接触る事になるんだろうか?

詳しい方、ヘルプ!