ctl::json

 ctl::jsonはC++用のheader-onlyなJSONテンプレートライブラリです。

目次

概要

 ctl::jsonはC++のjsonライブラリです。特徴は次の通りです。

 動作確認済みコンパイラのうちもっとも古いものはVC++2005です。

Download

使い方

 ctljson.hppをincludeするだけです。

読み込み

 ctl::json型インスタンスを作り、そのメンバ函数parse()にJSONデータ文字列(std::stringまたはnull終端文字列)を渡します。

//  Example 01:
ctl::json root;
const std::string jsondata = R"({
    "a": 1,
    "b": {
        "b1": "#",
        "b2": "%"
    }
})";
root.parse(jsondata);   //  JSONの読み込み。

printf(R"(["a"] = %d, ["b"]["b1"] = "%s")" "\n",
    root["a"].num<int>(),
    root["b"]["b1"].str().c_str());

//  出力結果:
//  ["a"] = 1, ["b"]["b1"] = "#"
		

 読み込みは、コンストラクタにJSONデータを渡すことによっても可能です。

//  Example 02:
ctl::json root(R"({
    "c": 2,
    "d": {
        "d1": "##",
        "d2": "%%"
    }
})");

printf(R"(["c"] = %d, ["d"]["d2"] = "%s")" "\n",
    root["c"].num<int>(),
    root["d"]["d2"].str().c_str());

//  出力結果:
//  ["c"] = 2, ["d"]["d2"] = "%%"
		

書き出し

 ctl::json型インスタンスのstringify()メンバ函数またはto_string()メンバ函数を呼び出すと、JSONデータ化されたstd::string型文字列が返ってきます。
 この2つのメンバ函数は名前が違うだけで挙動に違いはありません。前者はJavaScript由来の名前で、後者はC++風の名前です。

//  Example 03:
ctl::json root, childnode;

childnode(0) = "zero";
childnode(1) = "one";

root("obj") = childnode;
root("str") = "ABC";

const std::string jsondata = root.stringify();  //  JSONの書き出し。
//  const auto jsondata = root.stringify<std::string>();
//  テンプレート引数により、戻り値の型を明示的に指定することも可能。

printf("%s\n", jsondata.c_str());

//  出力結果:
//  {"obj":["zero","one"],"str":"ABC"}
		

データ型の種類とアクセス方法

 当ライブラリには ctl::json, ctl::wjson, ctl::u16json, ctl::u8json という4つの型が存在しますが、これらは「文字列を何型で保持するか」が異なるだけで使い方は基本的に同じです。以下では ctl::json を使って解説を行います。

 ctl::jsonインスタンスは、RFC 8259に定められた数値文字列配列オブジェクト、または true, false, null という3つのリテラル名」を値として保持することが出来ます。そして配列またはオブジェクトの各要素は、それ自体もctl::json型のインスタンスです。

 あるインスタンスが今現在どの型を保持しているかは、type()メンバ函数によって調べられます。この函数の戻り値はctl::json::value_typeという名前のenum型で、値としてnumber(数値), string(文字列), array(配列), object(オブジェクト), booleantruefalseのみ保持できる), null, unassigned, fallbackが定義されています。

 各タイプへのアクセス方法は以下の通りです。なお以下の説明に出てくるchar_type, string_typeという型は、ctl::json型インスタンスにおいてはそれぞれchar, std::stringtypedefです(その他の型については後述のの項を参照)。

数値 (ctl::json::number)
読み込み用メンバ函数 template <typename NumType>
  NumType num() const

double num() const
string_type numstr() const
書き込み用メンバ函数 ctl::json &operator=(double)
ctl::json &operator=(bool)
ctl::json &set_num(double, int precision)

 ECMAScript (JavaScript) は数値をIEEE 754の64-bit formatで保持します。そのため当ライブラリも、数値はdouble型で保管します。実装をシンプルにするため様々な整数型用のoverload函数はあえて用意していません。

 数値を数値として取り出したい時はnum()メンバ関数を用いてjs.num<int>()のように書きます。テンプレート引数を省略した時はdouble型で返されます。
 数値を文字列として返してほしい時はjs.numstr()のように書きます。

 IEEE 754の64-bit formatは仮数部が53 bitですので、それを上回るビット数の整数を読み書きしようとすると誤差が発生します。これはECMAScript由来の仕様です。
 また、お使いのコンパイラのdouble型がIEEE 754準拠ではない場合にも誤差が発生する可能性があります。精度の厳密さが求められる状況では、数値を文字列化して読み書きすることをお勧めします(js = 12.34 ではなく js = "12.34" のようにする)。

 operator=()を使った書き込みでは、小数は小数点以下6桁で丸められます。その際末尾の0は取り除かれ、小数点以下の数字がなくなってしまった場合は小数点そのものも取り除かれます。
 小数点以下の丸め精度を変えたい場合や、末尾の0を取り除いてほしくない場合はset_num()を使います。このメンバ函数の詳細な使い方については後述します。

文字列 (ctl::json::string)
読み込み用メンバ函数 string_type str() const
書き込み用メンバ函数 ctl::json &operator=(const string_type &)
ctl::json &operator=(const char_type *)
ctl::json &set_str(const string_type &)
ctl::json &set_str(const char_type *)
template <typename InputIterator>
  ctl::json &set_str(InputIterator begin, const InputIterator end)

 ctl::json, ctl::u8json: 文字列はすべてUTF-8として解釈されます。
 ctl::wjson, ctl::u16json: 文字列はすべてUTF-16として解釈されます。

[2.102まで]set_str()raw()という名前でした。operator=()による代入では渡された文字列に対してアンエスケープ処理が行われるのに対して、raw()による代入では、渡された文字列がそのまま値になるという違いがありました。
 例えば js = "\\u0026"jsの値が "&" となるのに対して、js.raw("\\u0026")"\u0026" がそのまま値となります。

true, false (ctl::json::boolean)
読み込み用メンバ函数 bool is_true() const
bool is_false() const
string_type numstr() const
書き込み用メンバ函数 ctl::json &operator=(bool)
ctl::json &set_bool(const bool)

 is_true()はインスタンスがboolean型かつその値がtrueならtrueを返します。is_false()はインスタンスがboolean型かつその値がfalseならtrueを返します。
 numstr()はインスタンスがboolean型ならその値を"true"または"false"という文字列で返します。

null (ctl::json::null)
読み込み用メンバ函数 bool is_null() const
string_type numstr() const
書き込み用メンバ函数 ctl::json &set_null()

 is_null()はインスタンスがnullならtrueを返します。
 numstr()はインスタンスがnullなら"null"という文字列を返します。

配列 (ctl::json::array)
読み込み用メンバ函数 ctl::json &operator[](const std::size_t pos)
ctl::json &operator()(const std::size_t pos)
書き込み用メンバ函数 ctl::json &operator[](const std::size_t pos)
ctl::json &operator()(const std::size_t pos)
追加 void push_back(const ctl::json &newnode)
挿入 bool insert(const std::size_t pos, ctl::json &newnode)
削除 bool erase(const std::size_t pos)
要素数の取得 std::size_t size() const

 配列の各要素の値は、それ自体がctl::json型のインスタンスです。従いまして、例えば「1番要素の2番目のデータ」には js[1][2] でアクセスすることが出来ます。

 js[0]のように書くのとjs(0)のように書くのとの違いについては後述します。

オブジェクト (ctl::json::object)
読み込み用メンバ函数 ctl::json &operator[](const string_type &key)
ctl::json &operator()(const string_type &key)
書き込み用メンバ函数 ctl::json &operator[](const string_type &key)
ctl::json &operator()(const string_type &key)
追加 void push_back(const ctl::json &newnode)
挿入 bool insert(const string_type &pos, const string_type &key, ctl::json &newnode)
削除 bool erase(const string_type &key)
要素数の取得 std::size_t size() const

 数値を添え字とする配列に対して、文字列を添え字とするのがオブジェクトです。プログラミング言語によって連想配列や辞書 (Dictionary) などとも呼ばれます。
 添え字が文字列であること以外は、配列と同じです。

 js["key"]という書き方とjs("key")という書き方との違いについては後述します。

 保持しているタイプと異なるタイプのデータを書き込んでもエラーとはなりません。タイプ情報もろとも書き換えられます。

2つのアクセス方法

 配列やオブジェクトについては、添え字の要素にアクセスする方法が2種類あります。

フォールバックノード

「フォールバックノード」とは、operator[]()を使ってJSONツリー上のデータにアクセスする際、該当する要素が存在しなかった場合に代わりにreturnされてくる特殊な要素です。

//  Example 04:
ctl::json root(R"({
    "a": 1,
    "b": 2
})");

printf(R"(root["c"] = %d, root("d") = %d)" "\n",
    root["c"].num<int>(),
    root("d").num<int>());
    //  ツリー上に存在しない ["c"] や ("d") にアクセスしている。

const std::string jsondata = root.stringify();  //  JSONの書き出し。
printf("%s\n", jsondata.c_str());

//  出力結果:
//  root["c"] = 0, root("d") = 0
//  {"a":1,"b":2,"d":null}
			

 上のExample 04では、operator[]()によってアクセスされた root["c"] のほうは要素が新規作成されていないのに対し、operator()()によってアクセスされた root("d") のほうは要素が作られてしまっています(値が null になっているのは何も代入されていないためです)。

 ある要素が実在する要素なのかフォールバックノードなのかは、exists()メンバ函数(実在するならtrueを返す)またはis_fallback()メンバ函数(フォールバックノードならtrueを返す)によって判別できます。

//  Example 05:
ctl::json root(R"({
    "a": 1
})");

printf(R"(["a"] = %d/%d)" "\n",
    root["a"].exists(),    //  root.exists("a") とも書ける。
    root["a"].is_fallback());

printf(R"(["b"] = %d/%d)" "\n",
    root["b"].exists(),
    root["b"].is_fallback());

//  出力結果:
//  ["a"] = 1/0
//  ["b"] = 0/1
			

 フォールバックノードの子孫はすべてフォールバックノードです。operator[]()を使えば、ツリー構造が変わってしまう心配をすることなく、安心して深い階層にアクセスすることが出来ます。

//  Example 06:
ctl::json root(R"({
    "a": 1
})");

printf(R"(["a"]["b"]["c"] = %d/%d)" "\n",
    root["a"]["b"]["c"].exists(),
    root["a"]["b"]["c"].is_fallback());

//  出力結果:
//  ["a"]["b"]["c"] = 0/1
			

 フォールバックノードに値を代入することは出来ません。左辺値にしてもエラーにはならず、単に無視されます。
 また、フォールバックノードの下の階層に()で要素を作ろうとしても無視されます。

//  Example 07:
ctl::json root(R"({
    "a": 1
})");

root["a"]["b"]("c") = 2; //  存在しない ["b"] の下に ("c") を作ろうとしている。

printf(R"(["a"]["b"]("c") = %d/%d %d)" "\n",
    root["a"]["b"]("c").exists(),
    root["a"]["b"]("c").num<int>(),
    root["a"].num<int>());

//  出力結果:
//  ["a"]["b"]("c") = 0/0 1
//  ["a"] の値は1という数値のまま。オブジェクト型に変わっていない。
			

API

 ctl::json, ctl::wjson, ctl::u16json, ctl::u8json classのメンバについての情報です。

string_typeとchar_type

 次の型がtypedefされています。

char_type string_type 備考
ctl::json charのtypedef. std::stringのtypedef.
ctl::u8json char8_tのtypedef. std::u8stringのtypedef. C++20以降のみ。
ctl::wjson wchar_tのtypedef. std::wstringのtypedef. WCHAR_MAXが0xffff以上、
0x110000未満の時のみ定義。
ctl::u16json char16_tのtypedef. std::u16stringのtypedef. C++11以降のみ。

 ctl::jsonctl::u8jsonとのstring_typeは、UTF-8文字列を格納します。
 ctl::wjsonctl::u16jsonとのstring_typeは、UTF-16文字列を格納します。

 表の備考欄にもあります通り、ctl::wjsonWCHAR_MAXが0xFFFF以上、0x110000未満の時のみ定義されます。事実上Windows用です。

定数

enum value_type

 インスタンスが保持しているデータの種別を表すものとして、次のものが定義されています。

enum value_type
{
    //  Version 2.109以降。
    array, object, number, string, boolean, null, unassigned, fallback

    //  Version 2.108まで。
    null, boolean, number, string, array, object, unassigned, fallback
};
				

 メンバ函数type()の返値がこの型です。

 numberは数値型、stringは文字列型、arrayは配列型、objectは文字を添え字とする配列型をそれぞれ意味します。
 booleantruefalseのみを値として保持できる型のことです。

 最後の2つは内部で使用する型です。引数無しのコンストラクタで作られたインスタンスはunassigned型になっています。

メンバ函数

コンストラクタ

 publicなものとしては、「空のインスタンスを作る」「別のインスタンスからコピーする」「文字列を受け取ってパーズする」「[begin, end) ペアを受け取ってパーズする」の4種類です。

json(); (1)
json(const json &right); (2)
template <typename StringType>
explicit json(const StringType &s);
(3)
template <typename ForwardIterator>
json(ForwardIterator begin, ForwardIterator end);
(4)

 この他、フォールバックノードを作るためのものがprivateなコンストラクタとして存在しています。

 Version 2.106以降、3つ目のコンストラクタの引数はstring_typeではなくなりましたので、任意の文字列型を渡すことも出来ます。渡された文字列は常にUTF-8として解釈されます。

void clear()

 値をclearし、種別をunassignedに戻します。

value_type type() const

 値の種別を返します。返り値はvalue_type型の整数です。

bool is_num() const

 保持している値の種類が数値ならtrueを返します。

bool is_str() const

 保持している値の種類が文字列ならtrueを返します。

bool is_bool() const

 保持している値の種類がboolean型ならtrueを返します。

bool is_true() const

 保持している値の種類がboolean型かつ値がtrueならtrueを返します。

bool is_false() const

 保持している値の種類がboolean型かつ値がfalseならtrueを返します。

bool is_null() const

 保持している値の種類がnullならtrueを返します。

bool is_array() const

 保持している値の種類が配列ならtrueを返します。

bool is_object() const

 保持している値の種類がオブジェクトならtrueを返します。

bool is_fallback() const

 フォールバックノードならtrue、そうでないならfalseを返します。

bool is_assigned() const

 有効な値を保持していればtrue、そうでないならfalseを返します。falseを返すのは「インスタンスが作られてからまだ何も代入されていない時」「clear()された直後」「直前のparse()が失敗した時」または「自身がフォールバックノードである場合」です。

bool exists() const

 実在する要素ならtrue、そうでないならfalseを返します。

bool exists(const std::size_t no) const

 保持している値の種類が配列の時、no < size()かどうかを返します。
 配列型ではないならfalseを返します。

bool exists(const string_type &key) const

 保持している値の種類がオブジェクトの時、keyという添え字の要素が存在するかどうかを返します。
 オブジェクト型ではないならfalseを返します。

json &operator=(const json &right) (1)
json &operator=(double right) (2)
json &operator=(bool right) (3)
json &operator=(const string_type &right) (4)
json &operator=(const char_type *right) (5)

 右辺値rightを自身(左辺値)にコピーします。

 数値からの代入の際は、その数値と一緒に数値を文字列化したものも内部に保存されます。これはprintf系函数を("%.6f", right)という引数によって呼び出したのと同等の文字列化を行った上で、さらに元の数値が小数なら小数点以下にある末尾の0を文字列から削り、その結果小数点以下の数値がすべてなくなったら小数点そのものも取り除くという処理を行ったものに相当します。
 この「文字列化された数値」は、stringify()to_stringによるデータ書き出し時に使用されます。

[2.102まで]文字列からの代入の際、文字列の中にエスケープされた文字(\" \t \nなど)があれば、それらはすべてアンエスケープされます。

template <typename StringType>
  bool parse(const StringType &s)
(1)
template <typename CharType>
  bool parse(const CharType *p)
(2)
template <typename ForwardIterator>
  ForwardIterator parse(ForwardIterator begin, ForwardIterator end)
(3)

 それぞれ文字列s、null終端文字列p[begin, end)を、UTF-8で符号化されたJSONデータと見なしてパーズします。
 最初の2つはパーズが完走したらtrueを返します。

 Version 2.106以降、最初の2つは引数がそれぞれstring_type, const char_type *ではなくなりましたので、任意の文字列型を渡すことも出来ます。

 最後のイテレータペアを引数として取るものは、パーズが終わった時点もしくは止まった時点のイテレータを戻り値として返してきます。この戻り値がendと同じかつis_assigned()trueであればパーズは無事完走したことを意味します。戻り値がendと同じなのにis_assigned()falseの時は、開き", [, {に対応する閉じ", ], }が最後まで現れずパーズが成功しなかった場合です。

 パーズの際、文字列中のエスケープされた文字(\" \t \nなど)についてはすべてアンエスケープされます。
 この点を除き、ctl::jsonctl::u8jsonとは入力文字列を解釈しません。たとえUTF-8として不正な並びがあっても素通しします。

 一方ctl::wjsonctl::u16jsonとは、UTF-16に変換できない箇所があればそこでパーズが止まります。

template <typename StringType>
  StringType stringify() const
(1)
template <typename StringType>
  StringType to_string() const
(2)
string_type stringify() const (3)
string_type to_string() const (4)

 保持している値をUTF-8形式のJSONデータに変換して返します。"\など、そのまま出力できない文字についてはエスケープ処理が行われます。

 stringify()to_string()とは名前が違うだけで挙動に違いはありません。前者はJavaScript由来の名前で、後者はC++風の名前です。

 フォールバックノードや種別がjson::unassignedのインスタンスは null を出力します。

 Version 2.106以降、戻り値の型はテンプレート引数によって明示的に指定することができます。省略時はstring_type型で返されます。

template <typename NumType>
  NumType num() const
(1)
double num() const (2)

 type() == json::numberの時は保持している数値を返します。
 type() == json::booleanの時は、保持している値がtrueなら1.0を、falseなら0.0をそれぞれ返します。
 type()がこれら以外の時は0.0を返します。

 Version 2.108以降、戻り値の型はテンプレート引数で指定できます。省略時はdouble型で返されます。

string_type numstr() const

 type() == json::numberの時は保持している数値を文字列化して返します。
 type() == json::booleanの時は、保持している値がtrueなら"true"というリテラル文字列を、falseなら"false"というリテラル文字列をそれぞれ返します。
 type() == json::nullなら"null"というリテラル文字列を返します。

 type()がこれら以外の時は空の文字列を返します。

 すべての種類のデータを文字列化してほしい時は、前記のstringify() / to_string()を使います。

string_type str() const

 type() == json::stringの時は保持している文字列を返します。
 type()がそれ以外の時は空の文字列を返します。

json &set_num(double d, int precision)

 precisionが正数の時は、printf系函数の("%.*f", precision, d)に相当する文字列化を行い、その文字列を値dとともに保持して書き出し時に使います。
 precisionが負数の時は、まずprecisionを絶対値化した上で先の処理を行います。そして値dが小数なら小数点以下にある末尾の0を削り、小数点以下の数値がすべてなくなったら小数点そのものも取り除くという処理を行います。

 この「文字列化された数値」は、stringify()to_stringによるデータ書き出し時に使用されます。

 ちなみに前記のoperator=(double d)は、set_num(d, -6)を呼び出しています。

json &set_str(const string_type &s) (1)
json &set_str(const char_type *const p) (2)
template <typename InputIterator>
  json &set_str(InputIterator begin, const InputIterator end)
(3)

 それぞれ文字列s、null終端文字列p[begin, end)を、UTF-8文字列と見なして自身(左辺値)にコピーします。

[2.102まで]raw()という函数名でした。operator=(const string_type &)が文字列のアンエスケープ処理を行うのに対して、こちらは行わないという違いがありました。

std::size_t size() const

 保持している値の種類が配列 (json::array) またはオブジェクト (json::object) の時、現在の要素数を返します。
 これら以外の種類の時は0を返します。

const json &operator[](const std::size_t no) const (1)
json &operator[](const std::size_t no) (2)

 保持している値の種類が配列かつno < size()なら、その要素への参照を返します。
 それ以外の時はフォールバックノードへの参照を返します。

const json &operator[](const string_type &key) const (1)
json &operator[](const string_type &key) (2)

 保持している値の種類がオブジェクトかつkeyという添え字の要素が存在するなら、その要素への参照を返します。
 それ以外の時はフォールバックノードへの参照を返します。

json &operator()(const std::size_t no)

 保持している値の種類が配列なら、(*this)[no]への参照を返します。
 no >= size()の時は、配列のサイズをno + 1に広げた上で(*this)[no]への参照を返します。

 保持している値の種類が配列ではない時は、配列型に変更してから上の処理を行います。

json &operator()(const string_type &key)

 保持している値の種類がオブジェクトなら、(*this)[key]への参照を返します。
 keyという名前の要素が存在しないなら新規に作った上で参照を返します。そしてオブジェクト内部の順番リストの最後にkeyが追加されます。

 保持している値の種類がオブジェクトではない時は、オブジェクト型に変更してから上の処理を行います。

void erase(const std::size_t no)

 保持している値の種類が配列かつno < size()なら、(*this)[no]を削除します。

void erase(const string_type &key)

 保持している値の種類がオブジェクトなら、(*this)[key]を削除します。

void insert(const std::size_t no, const json &right)

 保持している値の種類が配列かつno <= size()なら、(*this)[no]の前にrightを挿入します。

void insert(const string_type &pos, const string_type &key, const json &right)

 保持している値の種類がオブジェクトなら、(*this)[pos]の前にkeyを添え字とする要素を作り、そこへrightをコピーします。
 オブジェクト中に既にkeyを添え字とする要素があった場合、既存のものを消した上で前述の処理を行います。posという添え字の要素がなければ何もしません。

void push_back(const json &right)

 保持している値の種類が配列なら、配列の末尾にrightを追加します。

void push_back(const string_type &key, const json &right)

 保持している値の種類がオブジェクトなら、その末尾にkeyという添え字の要素としてrightを追加します。
 既にオブジェクト中にkeyという名前の要素が存在するなら、rightで上書きした上で最後尾に移動させます。

 オブジェクトに要素を新規追加する場合、通常はoperator()()による代入のほうをお使いください。このpush_back()keyという名前の要素が必ず最後になるような処理をする分、速度が遅めです。

json &set_bool(const bool b)

 保持している値の種類がboolean型にし、値をbにします。

json &set_null()

 保持している値をnullにします。

例外

 ctl::json/ctl::u8json固有の例外というものはありません。内部でnewが失敗するとstd::bad_allocが投げられるのみです。

Namespace

 当ライブラリ既定のnamespacectlですが、あらかじめ#define NAMESPACE_CTLJSON ~を定義しておくことにより変更することも出来ます。