LoginSignup
50

More than 5 years have passed since last update.

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

Posted at

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

詳しい方、ヘルプ!

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
50