ctl::json

 ctl::jsonはC++用の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",
    (int)root["a"].num(),
    root["b"]["b1"].str().c_str());

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

 std::string""の代わりにstd::u8string型インスタンスやu8""文字列リテラルから読み込みたい時は、ctl::u8json型を使います。

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

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

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

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

書き出し

 ctl::json型インスタンスのstringify()メンバ函数またはto_string()メンバ函数を呼び出すと、JSONデータ化されたstd::string型文字列が返ってきます。インスタンスがctl::u8json型の時は、std::u8string型が返ってきます。
 この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の書き出し。
printf("%s\n", jsondata.c_str());

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

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

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

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

 各タイプへのアクセス方法は以下の通りです。なお、説明に出てくるstring_typeという型は、そのインスタンスがctl::json型ならstd::stringtypedefで、ctl::u8json型ならstd::u8stringtypedefです。

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

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

 数値を数値として取り出したい時はnum()メンバ関数を用いてjs.num()のように書きます。数値を文字列として返してほしい時はjs.numstr()のように書きます。

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

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

文字列 (ctl::json::string)
読み込み用メンバ函数 string_type str() const
書き込み用メンバ函数 ctl::json &operator=(const string_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として解釈されます。

 operator=(const char_type *)はあえて用意していません。0を代入しようとするとoperator=(double)との間でoverloadの曖昧さが発生してしまうためです。

[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 &set_bool(const bool)

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

 operator=(const bool)はあえて用意していません。整数値を代入しようとするとoperator=(double)との間でoverloadの曖昧さが発生してしまうためです。

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",
    (int)root["c"].num(),
    (int)root("d").num());
    //  ツリー上に存在しない ["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(),
    (int)root["a"]["b"]("c").num(),
    (int)root["a"].num());

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

API

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

ctl::jsonnode

 ctl::jsonctl::u8jsonとの基底クラスです。次のように定義されています。

#if defined(__cplusplus) && (__cplusplus >= 201103L)
template <typename stringT,
    template <typename V1, typename... V2> class vector = std::vector,
    template <typename M1, typename M2, typename... M3> class map = std::map,
    template <typename L1> class less = std::less,
    template <typename A1> class alloc = std::allocator>
#else
template <typename stringT,
    template <typename V1, typename V2> class vector = std::vector,
    template <typename M1, typename M2, typename M3, typename T4> class map = std::map,
    template <typename L1> class less = std::less,
    template <typename A1> class alloc = std::allocator>
#endif
class jsonnode;

typedef jsonnode<std::string> json;

#if defined(__cpp_char8_t)
typedef jsonnode<std::u8string> u8json;
#endif
		

 template引数が込み入っているのは、カスタマイズしたコンテナを使えるようにするためです。通常はtypedefしたものであるctl::jsonまたはctl::u8jsonを使います。

 基底クラスであるctl::jsonnodeを直接使用することは想定していませんので、以下、ctl::jsonを使って説明を行います。

string_typeとchar_type

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

ctl::jsonctl::u8json
string_type std::stringのtypedef. std::u8stringのtypedef.
char_type charのtypedef. char8_tのtypedef.

定数

enum value_type

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

enum value_type
{
    null, boolean, number, string, array, object, unassigned, fallback
};
				

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

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

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

メンバ函数

コンストラクタ

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

json();
json(const json &right);
json(const string_type &s);
				

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

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 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)
json &operator=(double right)
json &operator=(const string_type &right)

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

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

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

bool parse(const string_type &s)
bool parse(const char_type *const p)
template <typename ForwardIterator>
  ForwardIterator parse(ForwardIterator begin, ForwardIterator end)

 それぞれ文字列s、null終端文字列p[begin, end)を、UTF-8で符号化されたJSONデータと見なしてパーズします。
 最初の2つはパーズが完走したらtrueを返します。最後のイテレータペアを引数として取るものは、パーズが終わった時点もしくは止まった時点のイテレータを戻り値として返してきます。この戻り値がendと同じであればパースは無事完走したことを意味します。

 パーズの際、文字列中のエスケープされた文字(\" \t \nなど)についてはすべてアンエスケープされます。

string_type stringify() const
string_type to_string() const

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

 この2つのメンバ函数は名前が違うだけで挙動に違いはありません。前者はJavaScript由来の名前で、後者はC++風の名前です。

double num() const

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

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)
json &set_str(const char_type *const p)
template <typename InputIterator>
  json &set_str(InputIterator begin, const InputIterator end)

 それぞれ文字列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
json &operator[](const std::size_t no)

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

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

 保持している値の種類がオブジェクトかつ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 ~を定義しておくことにより変更することも出来ます。