PicojsonのJSON書き出しをもっと便利にする

C++でPicojsonというJSONを読み書きするライブラリがあります。

JSONを読みだすときはそんなに苦じゃないのですが、このライブラリを使ってJSONの作成を始めると冗長な表現をする関係でタイプ数が増えて結構面倒なんですよね…

// いちいちこんな感じでコードを書かないといけない
picojson::object id;
id.insert(std::make_pair("id", picojson::value(1.0)));
id.insert(std::make_pair("data", picojson::value(data)));

なのでもっと簡単に(昔みたいに)使えるように書き込むときに使用する薄いラッパークラスを作成したいと思います。

Jsonクラス

picojson::objectをメンバーに持つサポートライブラリを次のように定義します。

ちょっと長いですが以下の通りです。

// JsonHelper.hpp

#pragma once
#pragma execution_character_set("utf-8")

#include "picojson.h"

#include <functional>

/**
 * picojson用の書き込み時に使うヘルパークラス
 */
class Json
{
    picojson::object obj;

public:

    operator picojson::object() { return this->obj; }
    operator picojson::value() { return picojson::value(this->obj); }

    picojson::object getObject() { return *this; }
    picojson::value toValue() { return *this; }

    inline void add(const std::string& key, const int value)
    {
        Json::add(this->obj, key, (double)value);
    }
    inline void add(const std::string& key, const unsigned int value)
    {
        Json::add(this->obj, key, (double)value);
    }
    inline void add(const std::string& key, const float value)
    {
        Json::add(this->obj, key, (double)value);
    }
    inline void add(const std::string& key, const bool value)
    {
        this->obj.insert(std::make_pair(key.c_str(), picojson::value(value)));
    }
    inline void addStr(const std::string& key, const std::string& value)
    {
        this->obj.insert(std::make_pair(key.c_str(), picojson::value(value)));
    }
    inline void add(const std::string& key, Json child)
    {
        this->obj.insert(std::make_pair(key.c_str(), child.toValue()));
    }
    
    template<class T>
    void add(const std::string& key, T value, const std::function<Json(T)>& convert)
    {
        this->obj.insert(std::make_pair(key.c_str(), convert(value).getValue()));
    }
    
    template<class T>
    void add(const std::string& key, const std::vector<T>& list, const std::function<Json(T)>& convert)
    {
        picojson::array jsonList;
        for (const auto& item : list)
        {
            jsonList.push_back(convert(item).toValue());
        }

        obj.insert(std::make_pair(key.c_str(), picojson::value(jsonList)));
    }

    std::string serialize()
    {
        return this->toValue().serialize();
    }

    // オブジェクトに値を追加する
    inline static void add(picojson::object& obj, const std::string& key, double value)
    {
        obj.insert(std::make_pair(key.c_str(), value));
    }
};

使い方

上記クラスの使い方ですが、すごい簡単な構造なら以下のように記述できます。

Json j;
j.add("key_1", 10);
j.add("key_2", 10.1f);
j.add("key_3", 10.2f);
j.add("key_4", (string)"あああ");
std::string msg = j.serialize();

で、子要素とか、配列を追加する場合以下の様に記述します。

// 配列用のクラス
class Sample
{
public:
    int a = 0;
    int b = 0;
};

// -----

Json j;
j.add("key_1", 10);
j.add("key_2", 10.1f);
j.add("key_3", 10.2f);
j.addStr("key_4", "あああ");

// 子要素の追加
Json child;
child.addStr("child", "aaa");
j.add("child", child);

// 配列の追加
// 元データの作成
std::vector<Sample> list;
for (int i = 0; i < 3; i++)
{
    Sample s;
    s.a = i;
    s.b = i;
    list.push_back(s);
}

// 配列として追加、変換規則を述語で指定
j.add<Sample>("list", list, [](Sample s)
{
    Json j;
    j.add("a", s.a);
    j.add("b", s.b);
    return j;
});

std::string msg = j.serialize();

// 上記コードはこんな感じに変換されます。
// {
//   "child": {
//     "child": "aaa"
//   },
//   "key_1": 10,
//   "key_2": 10.100000381469727,
//   "key_3": 10.199999809265137,
//   "key_4": "あああ",
//   "list": [
//     {
//       "a": 0,
//       "b": 0
//     },
//     {
//       "a": 1,
//       "b": 1
//     },
//     {
//       "a": 2,
//       "b": 2
//     }
//   ]
// }

階層構造はサポートしていないので1階層ごとにオブジェクトを作って上記操作を繰り返していきます。

なんか微妙に指定の仕方が間違ってるかもしれませんが、配列やオブジェクトの追加がラムダ式でできるので割とフレキシブルにノードの構築ができると思います。

以上です。