ctl::jsonはC++用のJSONテンプレートライブラリです。
ctl::jsonはC++のjsonライブラリです。特徴は次の通りです。
動作確認済みコンパイラのうちもっとも古いものはVC++2005です。
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_type
(ctl::u8json
型ならctl::u8json::value_type
)という名前のenum
型で、値としてnumber
(数値), string
(文字列), array
(配列), object
(オブジェクト), boolean
(true
かfalse
のみ保持できる), null
, unassigned
, fallback
が定義されています。
各タイプへのアクセス方法は以下の通りです。なお、説明に出てくるstring_type
という型は、そのインスタンスがctl::json
型ならstd::string
のtypedef
で、ctl::u8json
型ならstd::u8string
のtypedef
です。
読み込み用メンバ函数 | 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()
を使います。このメンバ函数の詳細な使い方については後述します。
読み込み用メンバ函数 | 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"
がそのまま値となります。
読み込み用メンバ函数 | 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の曖昧さが発生してしまうためです。
読み込み用メンバ函数 | bool is_null() const string_type numstr() const
|
---|---|
書き込み用メンバ函数 | ctl::json &set_null()
|
is_null()
はインスタンスがnullならtrue
を返します。
numstr()
はインスタンスがnullなら"null"
という文字列を返します。
読み込み用メンバ函数 | 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 &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種類あります。
operator()()
によるアクセス。読み書きとも常に実在する要素への参照を返します。配列の場合、添え字n
がその配列のサイズ以上である時は、まず配列をn+1
まで拡張した上で[n]
への参照を返します。オブジェクトの場合も、n
というキー名の要素がない時はまずそのような名前の要素を作り、その上でそこへの参照を返します。operator[]()
によるアクセス。該当する要素が存在している場合はそこへの参照を返しますが、存在しない場合は「フォールバックノード」という特殊な要素への参照を返し、要素を新規作成しません。ツリー構造を変えないことが保証されているアクセス方法です。
「フォールバックノード」とは、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という数値のまま。オブジェクト型に変わっていない。
ctl::json
/ ctl::u8json
classのメンバについての情報です。
ctl::json
とctl::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
を使って説明を行います。
次の型がtypedefされています。
ctl::json | ctl::u8json | |
---|---|---|
string_type | std::string のtypedef. |
std::u8string のtypedef. |
char_type | char のtypedef. |
char8_t のtypedef. |
インスタンスが保持しているデータの種別を表すものとして、次のものが定義されています。
enum value_type { null, boolean, number, string, array, object, unassigned, fallback };
メンバ函数type()の返値がこの型です。
number
は数値型、string
は文字列型、array
は配列型、object
は文字を添え字とする配列型をそれぞれ意味します。
boolean
はtrue
かfalse
のみを値として保持できる型のことです。
最後の2つは内部で使用する型です。引数無しのコンストラクタで作られたインスタンスはunassigned
型になっています。
public
なものとしては、「空のインスタンスを作る」「別のインスタンスからコピーする」「文字列を受け取ってパーズする」の3種類です。
json(); json(const json &right); json(const string_type &s);
この他、フォールバックノードを作るためのものがprivate
なコンストラクタとして存在しています。
値をclearし、種別をunassigned
に戻します。
値の種別を返します。返り値はvalue_type型の整数です。
保持している値の種類が数値ならtrue
を返します。
保持している値の種類が文字列ならtrue
を返します。
保持している値の種類がboolean型ならtrue
を返します。
保持している値の種類がboolean型かつ値がtrue
ならtrue
を返します。
保持している値の種類がboolean型かつ値がfalse
ならtrue
を返します。
保持している値の種類がnull
ならtrue
を返します。
保持している値の種類が配列ならtrue
を返します。
保持している値の種類がオブジェクトならtrue
を返します。
フォールバックノードならtrue
、そうでないならfalse
を返します。
実在する要素ならtrue
、そうでないならfalse
を返します。
保持している値の種類が配列の時、no < size()
かどうかを返します。
配列型ではないならfalse
を返します。
保持している値の種類がオブジェクトの時、key
という添え字の要素が存在するかどうかを返します。
オブジェクト型ではないならfalse
を返します。
右辺値right
を自身(左辺値)にコピーします。
数値からの代入の際は、その数値と一緒に数値を文字列化したものも内部に保存されます。これはprintf
系函数を("%.6f", right)
という引数によって呼び出したのと同等の文字列化を行った上で、さらに元の数値が小数なら小数点以下にある末尾の0を文字列から削り、その結果小数点以下の数値がすべてなくなったら小数点そのものも取り除くという処理を行ったものに相当します。
この「文字列化された数値」は、stringify()
やto_string
によるデータ書き出し時に使用されます。
[2.102まで]文字列からの代入の際、文字列の中にエスケープされた文字(\" \t \nなど)があれば、それらはすべてアンエスケープされます。
それぞれ文字列s
、null終端文字列p
、[begin, end)
を、UTF-8で符号化されたJSONデータと見なしてパーズします。
最初の2つはパーズが完走したらtrue
を返します。最後のイテレータペアを引数として取るものは、パーズが終わった時点もしくは止まった時点のイテレータを戻り値として返してきます。この戻り値がend
と同じであればパースは無事完走したことを意味します。
パーズの際、文字列中のエスケープされた文字(\" \t \nなど)についてはすべてアンエスケープされます。
保持している値をUTF-8形式のJSONデータに変換して返します。"
や\
など、そのまま出力できない文字についてはエスケープ処理が行われます。
この2つのメンバ函数は名前が違うだけで挙動に違いはありません。前者はJavaScript由来の名前で、後者はC++風の名前です。
type() == json::number
の時は保持している数値を返します。
type() == json::boolean
の時は、保持している値がtrue
なら1.0
を、false
なら0.0
をそれぞれ返します。
type()
がこれら以外の時は0.0
を返します。
type() == json::number
の時は保持している数値を文字列化して返します。
type() == json::boolean
の時は、保持している値がtrue
なら"true"
というリテラル文字列を、false
なら"false"
というリテラル文字列をそれぞれ返します。
type() == json::null
なら"null"
というリテラル文字列を返します。
type()
がこれら以外の時は空の文字列を返します。
すべての種類のデータを文字列化してほしい時は、前記のstringify()
/ to_string()
を使います。
type() == json::string
の時は保持している文字列を返します。
type()
がそれ以外の時は空の文字列を返します。
precision
が正数の時は、printf
系函数の("%.*f", precision, d)
に相当する文字列化を行い、その文字列を値d
とともに保持して書き出し時に使います。
precision
が負数の時は、まずprecision
を絶対値化した上で先の処理を行います。そして値d
が小数なら小数点以下にある末尾の0を削り、小数点以下の数値がすべてなくなったら小数点そのものも取り除くという処理を行います。
この「文字列化された数値」は、stringify()
やto_string
によるデータ書き出し時に使用されます。
ちなみに前記のoperator=(double d)
は、set_num(d, -6)
を呼び出しています。
それぞれ文字列s
、null終端文字列p
、[begin, end)
を、UTF-8文字列と見なして自身(左辺値)にコピーします。
[2.102まで]raw()
という函数名でした。operator=(const string_type &)
が文字列のアンエスケープ処理を行うのに対して、こちらは行わないという違いがありました。
保持している値の種類が配列 (json::array
) またはオブジェクト (json::object
) の時、現在の要素数を返します。
これら以外の種類の時は0を返します。
保持している値の種類が配列かつno < size()
なら、その要素への参照を返します。
それ以外の時はフォールバックノードへの参照を返します。
保持している値の種類がオブジェクトかつkey
という添え字の要素が存在するなら、その要素への参照を返します。
それ以外の時はフォールバックノードへの参照を返します。
保持している値の種類が配列なら、(*this)[no]
への参照を返します。
no >= size()
の時は、配列のサイズをno + 1
に広げた上で(*this)[no]
への参照を返します。
保持している値の種類が配列ではない時は、配列型に変更してから上の処理を行います。
保持している値の種類がオブジェクトなら、(*this)[key]
への参照を返します。
key
という名前の要素が存在しないなら新規に作った上で参照を返します。そしてオブジェクト内部の順番リストの最後にkey
が追加されます。
保持している値の種類がオブジェクトではない時は、オブジェクト型に変更してから上の処理を行います。
保持している値の種類が配列かつno < size()
なら、(*this)[no]
を削除します。
保持している値の種類がオブジェクトなら、(*this)[key]
を削除します。
保持している値の種類が配列かつno <= size()
なら、(*this)[no]
の前にright
を挿入します。
保持している値の種類がオブジェクトなら、(*this)[pos]
の前にkey
を添え字とする要素を作り、そこへright
をコピーします。
オブジェクト中に既にkey
を添え字とする要素があった場合、既存のものを消した上で前述の処理を行います。pos
という添え字の要素がなければ何もしません。
保持している値の種類が配列なら、配列の末尾にright
を追加します。
保持している値の種類がオブジェクトなら、その末尾にkey
という添え字の要素としてright
を追加します。
既にオブジェクト中にkey
という名前の要素が存在するなら、right
で上書きした上で最後尾に移動させます。
オブジェクトに要素を新規追加する場合、通常はoperator()()による代入のほうをお使いください。このpush_back()
はkey
という名前の要素を必ず最後尾に移動させるための処理の分、速度が遅めです。
保持している値の種類がboolean
型にし、値をb
にします。
保持している値をnull
にします。
ctl::json/ctl::u8json固有の例外というものはありません。内部でnew
が失敗するとstd::bad_alloc
が投げられるのみです。
当ライブラリ既定のnamespace
はctl
ですが、あらかじめ#define NAMESPACE_CTLJSON ~
を定義しておくことにより変更することも出来ます。