C++11 or later で JSON 文字列から静的なクラス(or struct)へ値を復元する(StaticJSON,  jsoncons, spotify-json, nlohmann json)

## 背景

C++ で JSON を扱うライブラリはいくつかありますが(picojson, rapidjson, json11, nlohmann json など), 具体的なメッセージ構造(クラス, struct)が決まっているときに復元するのはいろいろ型変換や型チェックのコードを書く必要がありめんどい.
(グラフィックスとか機械学習などの用途では, だいたいメッセージ型が決まっているため)

protobuf, flatbuffers のように, スキーマ定義などから JSON を具体的な静的な型を持つメッセージに復元したい.
たとえば以下のような感じ

```c++
struct Message {
  std::string name;
  int id;
};

std::string str = "....";
Message message;

message = message_from_json(str);
```

## ライブラリ

すでに 4 つ良さげなライブラリがあります. ありがたく使わせていただきましょう.

### StaticJSON

https://github.com/netheril96/StaticJSON

C++11

JSON の処理には RapidJSON を使っています. パッケージでは v1.1(2016-8-25) を使っていますが, 最新の RapidJSON `master` でも, 少なくともコンパイルはうまくいくのを確認しました.

std::string, int, std::map あたりの基本型であれば, デフォルトでよろしく復元してくれます.

カスタムのクラスの場合は, `protoc`, `flatc` のように `autojsoncxx` でスキーマ定義から python でコードを生成します.
  
C++ コードから, JSON スキーマ定義を出力することもできます.

#### enum を扱う

enum 対応しています.

https://github.com/netheril96/StaticJSON#register-enumeration-types

#### 階層構造の struct を扱う.

以下のように, struct にそれぞれ `staticjson_init` でマッピングを書けばいけます.

```
struct BlendShapeTargetJ
{
  float weight;
  std::string mesh_filename;

  void staticjson_init(staticjson::ObjectHandler* h)
  {
      h->add_property("weight", &weight);
      h->add_property("mesh", &mesh_filename);
      h->set_flags(staticjson::Flags::DisallowUnknownKey);
  }
};

struct BlendShapeJ
{
  std::string name;
  std::vector<BlendShapeTargetJ> targets;

  void staticjson_init(staticjson::ObjectHandler* h)
  {
      h->add_property("name", &name);
      h->add_property("targets", &targets);
      h->set_flags(staticjson::Flags::DisallowUnknownKey);
  }
};

struct RootJ
{
  std::vector<BlendShapeJ> blendshapes;

  void staticjson_init(staticjson::ObjectHandler* h)
  {
      h->add_property("blendshapes", &blendshapes);
      h->set_flags(staticjson::Flags::DisallowUnknownKey);
  }
};

...

void Parse(const std::string &in_str) 
{
  RootJ root;

  staticjson::ParseStatus res;
  if (!staticjson::from_json_string(in_str.c_str(), &root, &res)) {
    std::cerr << "Failed to parse JSON.\n";
    std::cerr << res.description() << "\n";
  }
}
```

とてもすっきり書けますね!
パースエラーなどは `staticjson::ParseStatus` を `from_json_string` に渡すことで取得できます.

メッセージ構造体に `staticjson_init` を含めたくない(staticjson への依存関係を無くしたい)ときは, non-intrusive にやる方法もあります.

https://github.com/netheril96/StaticJSON#non-intrusive-definition

```c++
namespace staticjson
{
void init(Date* d, ObjectHandler* h)
{
    h->add_property("year", &d->year);
    h->add_property("month", &d->month);
    h->add_property("day", &d->day);
    h->set_flags(Flags::DisallowUnknownKey);
}
}
```

と, `staticjson` に struct/class ごとに `init` を定義します.

なぜこれで動くのかというと, まず `init` がテンプレートで定義されていて, テンプレートではクラスにある `staticjson_init()` を呼ぶようになっています.

https://github.com/netheril96/StaticJSON/blob/master/include/staticjson/basic.hpp#L253

したがって `init` を書き換える(テンプレート特殊化で上書き)ことで, non-intrusive 化を実現しているわけですね.

#### メンバーを optional にする

デフォルトでは, JSON にプロパティ(メンバー)がみつからないとパースエラーになります.

```
void init(Date* d, ObjectHandler* h)
{
    h->add_property("year", &d->year, staticjson::Flags::Optional);
    h->add_property("month", &d->month);
    h->add_property("day", &d->day);
}
```

と,`add_property` の引数で optional にできます(`year` がみつからなくてもパースエラーにはしない)

#### std::optional のサポート

JSON にメンバ(key)がなかったら実体を作らない(デフォルト値で実体を作るのが向いていないケース), というのをやりたいときもあります.

C++17 std::optional に対応していました!

https://github.com/netheril96/StaticJSON/blob/master/test/test_integration.cpp

`optional_support.hpp` をインクルードすればいけます.
コードはそんなに複雑ではないので, optional-lite などを使うように書き換えれば C++11 でも動かせるでしょう.

### jsoncons

https://github.com/danielaparker/jsoncons

C++11. C++ コードに直接メッセージ定義を register して使えます.
json のパースには自前のパーサを使っていますので, 普通の json ライブラリとしても利用できます.

CSV や CBOR(バイナリフォーマット), messagepack も対応していたりと, なかなか機能豊富です.
しかも Boost license.
gcc 4.8(RHEL/CentOS7 default) での std::regex 問題もうまく対応していますね.
 
C++ プログラムの他の部分で rapidjson など他の json ライブラリを使っている場合は, うまく住み分けできるかどうか考える必要があります.

### spotify-json

https://github.com/spotify/spotify-json

double-conversion ライブラリを使っていて, 浮動小数点数をより正確に扱えます.

StaticJSON よりいろいろな機能があります. enum なども扱えます.

https://github.com/spotify/spotify-json/blob/master/doc/api.md#enumeration_t
 
any 型で opaque な型も扱えますし, encode(C++ 構造体から JSON string)の機能もあります.

明示的な記載はありませんが, Android でもコンパイルできましたので Android でも動作するものと思われます.

ただ, Windows(MSVC)では CMake でランタイムライブラリを強制的に切り替えていますので, 自分のアプリに組み込んだときなどにランタイムライブラリの違いでリンクがうまくいかない時があります.

### nlohmann JSON

nlohmann JSON でも構造体へ, 構造体から JSON 変換できました.

https://github.com/nlohmann/json#basic-usage

https://github.com/nlohmann/json/issues/1648

optional なものには try_get_to が使えそうです.

https://github.com/nlohmann/json/issues/1563

v3.9 から, `NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE` などマクロで一括定義もありました!

https://github.com/nlohmann/json#simplify-your-life-with-macros

#### optional 

パラメータが optional な場合, 楽にやる方法はありません(`std::optional` を直接は使えない)

結局は自前でいろいろやることになります.

```c++
struct Settings
{
  std::string name;
  std::array<float, 3> v{0.0f, 0.0f, 0.0f};
  std::optional<int> flag;
};

void to_json(nlohmann::json &j, const Settings &d) {
  j = nlohmann::json{{"name", d.name}, {"v", d.v}};
  // optional
  if (d.flag) {
    j["flag"] = d.flag.value();
  }
}

void from_json(const nlohmann::json &j, Settings &d) {
  j.at("name").get_to(d.name);

  j.at("v").get_to(d.v);

  // optional
  if (j.contains("flag")) {
    int flag;
    j.at("flag").get_to(flag);
    d.flag = flag;
  }
}

}
```

こんな感じでしょうか.

json.hpp は使うのは簡単なぶん, json.hpp はコンパイルが劇重になるのが欠点ですね.

### どれを使う?

とりあえずぺろっと使いたい場合は jsoncons,
jsoncons ほどの機能はいらない, rapidjson を使いたい(schema validation や schema 出力をやりたい. jsoncons では schema 機能は無い)などの場合は StaticJSON でしょうか

spotify-json, json.hpp は玄人むけ.

筆者は最近は StaticJSON がお気に入りですね.

## todo

* [x] recursive 可能な std::variant を使って, 自前で JSON -> メッセージ構造体への復元のコードを綺麗にかけないか思いを馳せる.
  * => 素直に spotify-json or StaticJSON を使ったほうがいいことがわかりました.