C++のjsonライブラリ決定版 nlohmann-json

C++のJSONライブラリには、例えばpicojsonとかboost/property_treeとかいくつかのものがあるが、それらのライブラリの中でもnlohmann-jsonライブラリが使いやすさや完成度、使用実績の点で群を抜いている。

多くのプロジェクトで使われている人気のあるライブラリなのだが、日本語の記事があまりないので本家のreadmeから重要な部分を要約する形で紹介する。

githubページはこちら。
https://github.com/nlohmann/json

この記事で使ったソースコード全体のgistはこちらにある。
https://gist.github.com/yohm/27d69509403b593778b2564e61bbc871

# 特徴

- 直感的なシンタックス
- ヘッダオンリー
  - 標準C++11で書かれていて外部ライブラリへの依存無し
  - homebrewでもインストールできる
- 高い信頼性
  - ユニットテストもしっかり書いてある（カバレッジ100%！）
  - macOSやiOSでも使われているらしい（！）
- 豊富な機能
  - STLや自前クラスとの変換
  - リテラルから初期化しやすい。stringリテラルからJSONへの変換
  - インデント幅を指定したpretty-printもできる
  - BSON, msgpackなどのバイナリへの変換（！）
  - JSON間のdiffを取ったりマージしたりもできる
- 商用利用も可能なオープンソースライセンス
  - MITライセンス

多くのプロジェクトで使われているらしくgithub上のstar数17.6Kになっている。
また必要そうな機能はすべて一式入っていて、C++らしいインターフェースになっている。
よほど強いこだわりがなければこれを使っておけばOKでしょう。

# 使い方

## インストール方法

headerファイルをインクルードするだけ。
もしくはhomebrewをつかってインストールすることもできる。

```shell-session
brew tap nlohmann/json
brew install nlohmann-json
```

## jsonクラスの使い方

以後、表記を簡潔にするため次の様なnamespaceを使う。

```cpp
#include <nlohmann/json.hpp>

// for convenience
using json = nlohmann::json;
```

JSONのデータを`nlohmann::json`クラスのインスタンスに格納して使う。
例えば、以下の様なオブジェクトを作りたい場合

```json
{
  "pi": 3.141,
  "happy": true,
  "name": "Niels",
  "nothing": null,
  "answer": {
    "everything": 42
  },
  "list": [1, 0, 2],
  "object": {
    "currency": "USD",
    "value": 42.99
  }
}
```

jsonオブジェクトに対して、mapの様にデータを追加していくことができる。

```cpp
  json j;
  j["pi"] = 3.141;
  j["happy"] = true;
  j["name"] = "Niels";
  j["nothing"] = nullptr;
  j["answer"]["everything"] = 42;  // 存在しないキーを指定するとobjectが構築される
  j["list"] = { 1, 0, 2 };         // [1,0,2]
  j["object"] = { {"currency", "USD"}, {"value", 42.99} };  // {"currentcy": "USD", "value": 42.99}

  std::cout << j << std::endl;  // coutに渡せば出力できる。
```

もしくは初期化リストを渡すともっと簡単に初期化できる。

```cpp
  json j2 = {
    {"pi", 3.141},
    {"happy", true},
    {"name", "Niels"},
    {"nothing", nullptr},
    {"answer", {
      {"everything", 42}
               }
    },
    {"list", {1, 0, 2}},
    {"object", {
      {"currency", "USD"},
      {"value", 42.99}
               }
    }
  };
```

ちなみに空の配列、objectは以下の様にかける。

```cpp
  json empty_list = json::array();  // []
  json empty_obj = json::object();  // {}
```

と書くことができる。

## jsonオブジェクトとjson文字列の変換

JSON形式の文字列リテラルから`json`インスタンスを構築することができる。

```cpp
 json j = R"({ "happy": true, "pi": 3.141 })"_json;
```

ここで`_json`という見慣れないサフィックスがあるが、これはC++11で導入されたユーザー定義リテラルというもの。
https://cpprefjp.github.io/lang/cpp11/user_defined_literals.html
これを使うと文字列リテラルにsuffixをつけた時のメソッドを定義でき、このライブラリでは`json`クラスのインスタンスに変換するメソッドが定義されている。

ちなみにプレフィックスの`R`はC++11で導入された生文字列リテラル。`"`などの文字をエスケープする必要がなくなる。
https://cpprefjp.github.io/lang/cpp11/raw_string_literals.html

リテラルじゃない文字列を変換するには`json::parse`関数を使う。

```cpp
std::string s = R"({ "happy": true, "pi": 3.141 } )";
json j = json::parse(s);
```

逆に、`json`インスタンスをJSON形式の文字列にシリアライズするには`dump`メソッドを使う。

```cpp

std::string s = j.dump();    // {\"happy\":true,\"pi\":3.141}
```

`dump`の引数に数字を渡すことでpretty printされた（整形された）文字列を出すことができる。引数の数字はインデントの幅。例えば、`j.dump(4);` とすると出力は以下の様なJSON文字列になる。

```json
{
    "happy": true,
    "pi": 3.141
}
```

istream, ostreamに渡すと暗黙的にいい感じにserialize,deserializeできる。
例えば、以下のように直感的な書き方ができる。
また最初の例で`std::cout`に渡すとうまくシリアライズされた文字列が出力されたのは、この暗黙的なシリアライズが行われているから。

```cpp
std::ifstream i("file.json");
json j;
i >> j;

std::ofstream o("pretty.json");
o << std::setw(4) << j << std::endl;   // std::setw でインデント幅を指定できる。
```

## STLのようなアクセス

jsonオブジェクトはmap(objectの場合)やvector(arrayの場合)の様に扱うことができる。

### Arrayの場合

まずはarrayの様に扱う場合についての例を以下に示す。任意の型を入れられるvectorのように振舞う。

```cpp
// create an array using push_back
json j;
j.push_back("foo");
j.push_back(1);
j.push_back(true);

// also use emplace_back
j.emplace_back(1.78);

// iterate the array
for (json::iterator it = j.begin(); it != j.end(); ++it) {
  std::cout << *it << '\n';
}

// range-based for
for (auto& element : j) {
  std::cout << element << '\n';
}

// getter/setter
const auto tmp = j[0].get<std::string>();
j[1] = 42;
bool foo = j.at(2);

// comparison
if( j == "[\"foo\", 42, true, 1.78]"_json ) {
  std::cout << "Equal" << std::endl;
} else {
  std::cout << "Not equal" << std::endl;
}

// other stuff
j.size();     // 4
j.empty();    // false
j.clear();    // =>  j == []

```


要素を取り出すところで少し注意が必要になる。
`operator[]`で取り出したあとはまだjsonインスタンスなので数値や文字列などに`get<std::string>()`などを用いて変換する必要がある。
この時、文字列のタイプをintegerなどの違う型に変換しようとすると例外が飛ぶ。


```cpp
const auto tmp = j[0].get<int>();  // stringが格納されているのにintに変換しようとする
// libc++abi.dylib: terminating with uncaught exception of type nlohmann::detail::type_error: [json.exception.type_error.302]
// type must be number, but is string
```

また`at()`で取り出したときの返り値の型とjsonの型が異なっている場合も例外が起きる。

```cpp
std::string foo = j.at(2);  // boolが入っているのにstringに変換しようとする
// libc++abi.dylib: terminating with uncaught exception of type nlohmann::detail::type_error: [json.exception.type_error.302]
// type must be string, but is boolean
```

要素の型をチェックするには`is_null`, `is_boolean`, `is_number`, `is_object`, `is_array`, `is_string`メソッドを使う。

```cpp
    json j = R"( ["foo", 1, true, null, []] )"_json;
    for(const json& x: j) {
      std::string type;
      if( x.is_null() ) { type = "null"; }
      else if( x.is_boolean() ) { type = "boolean"; }
      else if( x.is_number() ) { type = "number"; }
      else if( x.is_object() ) { type = "object"; }
      else if( x.is_array() ) { type = "array"; }
      else if( x.is_string() ) { type = "string"; }
      std::cout << type << std::endl;
    }
```

### Objectの場合
objectの場合

```cpp
// create an object
json o;
o["foo"] = 23;
o["bar"] = false;
o["baz"] = 3.141;

// also use emplace
o.emplace("weather", "sunny");

// special iterator member functions for objects
for (json::iterator it = o.begin(); it != o.end(); ++it) {
  std::cout << it.key() << " : " << it.value() << "\n";
}

// the same code as range for
for (auto& el : o.items()) {
  std::cout << el.key() << " : " << el.value() << "\n";
}

// find an entry
if (o.find("foo") != o.end()) {
  // there is an entry with key "foo"
}

// or simpler using count()
int foo_present = o.count("foo"); // 1
int fob_present = o.count("fob"); // 0

// delete an entry
o.erase("foo");
```

## STLコンテナからのjsonインスタンスへの変換

配列のコンテナ(std::array, std::vector, std::deque, std::forward_list, std::list) で、その要素がJSONの値として使えるもの(e.g., integers, floating point numbers, Booleans, string types, or again STL containers described in this section)は、jsonのarrayに変換される。

```cpp
std::vector<int> c_vector {1, 2, 3, 4};
json j_vec(c_vector);
// [1, 2, 3, 4]
```

同様に、連想配列 (std::map, std::multimap, std::unordered_map, std::unordered_multimap) で、keyが文字列に変換可能なもの、かつvalueがJSONの値に変換できるもの (数値、文字列、boolean、他のSTL) はJSONのobjectに変換できる。
（ただし、multimapは最初に出てきたkeyの値が使われる。）

```cpp
std::map<std::string, int> c_map { {"one", 1}, {"two", 2}, {"three", 3} };
json j_map(c_map);
// {"one": 1, "three": 3, "two": 2 }
```

## 暗黙的な変換

https://github.com/nlohmann/json#implicit-conversions

いくつかの型（文字列、数値、boolean）は暗黙的にJSON型に変換できる。

```cpp
// strings
std::string s1 = "Hello, world!";
json js = s1;
auto s2 = js.get<std::string>();
```

## 任意の型への変換

https://github.com/nlohmann/json#arbitrary-types-conversions

例えば以下の様なPersonクラスがあったとする。

```cpp
namespace ns {
    // a simple struct to model a person
    struct person {
        std::string name;
        std::string address;
        int age;
    };
}

ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
```

ユーザー定義クラスをjsonに暗黙的に変換するためには、以下の様に２つの関数`to_json`, `from_json`を定義する必要がある。 
この２つの関数を定義しておけば、jsonのコンストラクタが呼ばれた時に自動的に`to_json`が呼ばれる。
また、`get<MY_TYPE>()` `get_to(MY_TYPE& arg)` が呼ばれた時に、自動的に`from_json`が呼ばれる。

```cpp
namespace ns {
    void to_json(json& j, const person& p) {
        j = json{{"name", p.name}, {"address", p.address}, {"age", p.age}};
    }

    void from_json(const json& j, person& p) {
        j.at("name").get_to(p.name);       // get_to(T& arg) は arg = get<T>() と同じ
        j.at("address").get_to(p.address);
        j.at("age").get_to(p.age);
    }
} // namespace ns
```

一度、変換関数を書いてしまえば、次の様にpersonインスタンスとjsonインスタンスを直感的に変換することができる。

```cpp
ns::person p = {"Ned Flanders", "744 Evergreen Terrace", 60};
json j = p;  // jsonへの暗黙的変換
std::cout << j << std::endl;
// {"address":"744 Evergreen Terrace","age":60,"name":"Ned Flanders"}

ns::person p2 = j.get<ns::person>();   // json->personへの変換
```

注意点としては、

- ユーザー定義型と上記の２つの関数は同じnamespaceで定義されていなければならない。(global namespaceでもよい）
- default constructorが（暗黙的でもよいので）定義されていなければならない。
- `from_json`の中では`operator[]`ではなくて`at()`を使うべき。そうしないとkeyが存在しない時の振る舞いが未定義となり、例外処理もできない。

boostなどのサードパーティーライブラリで既存のnamespaceに追加することはしたくない場合は[こちら](https://github.com/nlohmann/json#how-do-i-convert-third-party-types)を参照。
default constructorが定義されていない場合でもムーブコンストラクタが定義されていれば同様に変換できる。[こちら](https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types)を参照。

## バイナリフォーマット

https://github.com/nlohmann/json#binary-formats-bson-cbor-messagepack-and-ubjson

このライブラリはJSONのライブラリなのに、なんとBSONやMessagePackなどのバイナリフォーマットへの変換までサポートしている。この辺りは他のJSONライブラリにはない特徴だと思う。
MessagePackとの相互変換は`to_msgpack`,`from_msgpack`関数を使って簡単にできる。

```cpp
    json j = R"({"foo": 1, "bar": "hello", "baz": true} )"_json;
    std::vector<std::uint8_t> v_msgpack = json::to_msgpack(j);

    json j2 = json::from_msgpack(v_msgpack);
    std::cout << j2 << std::endl;  // == {"foo": 1, "bar": "hello", "baz": true}
```