ctl::jsonはC++用のheader-onlyなJSONテンプレートライブラリです。
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) |
template <typename StringType> StringType stringify() const |
(1) |
template <typename StringType> StringType to_string() const |
(2) |
string_type to_string() const | (3) |
string_type stringify() const | (4) |
template <typename NumType> NumType num() const |
(1) |
double num() const | (2) |
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) |
const json &operator[](const std::size_t no) const | (1) |
json &operator[](const std::size_t no) | (2) |
const json &operator[](const string_type &key) const | (1) |
json &operator[](const string_type &key) | (2) |
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", 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
(オブジェクト), boolean
(true
かfalse
のみ保持できる), null
, unassigned
, fallback
が定義されています。
各タイプへのアクセス方法は以下の通りです。なお以下の説明に出てくるchar_type
, string_type
という型は、ctl::json
型インスタンスにおいてはそれぞれchar
, std::string
のtypedef
です(その他の型については後述の型の項を参照)。
読み込み用メンバ函数 | template <typename NumType> 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()
を使います。このメンバ函数の詳細な使い方については後述します。
読み込み用メンバ函数 | 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"
がそのまま値となります。
読み込み用メンバ函数 | 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"
という文字列で返します。
読み込み用メンバ函数 | 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", 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という数値のまま。オブジェクト型に変わっていない。
ctl::json
, ctl::wjson
, ctl::u16json
, ctl::u8json
classのメンバについての情報です。
次の型が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::json
とctl::u8json
とのstring_type
は、UTF-8文字列を格納します。
ctl::wjson
とctl::u16json
とのstring_type
は、UTF-16文字列を格納します。
表の備考欄にもあります通り、ctl::wjson
はWCHAR_MAX
が0xFFFF以上、0x110000未満の時のみ定義されます。事実上Windows用です。
インスタンスが保持しているデータの種別を表すものとして、次のものが定義されています。
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
は文字を添え字とする配列型をそれぞれ意味します。
boolean
はtrue
かfalse
のみを値として保持できる型のことです。
最後の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として解釈されます。
値をclearし、種別をunassigned
に戻します。
値の種別を返します。返り値はvalue_type型の整数です。
保持している値の種類が数値ならtrue
を返します。
保持している値の種類が文字列ならtrue
を返します。
保持している値の種類がboolean型ならtrue
を返します。
保持している値の種類がboolean型かつ値がtrue
ならtrue
を返します。
保持している値の種類がboolean型かつ値がfalse
ならtrue
を返します。
保持している値の種類がnull
ならtrue
を返します。
保持している値の種類が配列ならtrue
を返します。
保持している値の種類がオブジェクトならtrue
を返します。
フォールバックノードならtrue
、そうでないならfalse
を返します。
有効な値を保持していればtrue
、そうでないならfalse
を返します。false
を返すのは「インスタンスが作られてからまだ何も代入されていない時」「clear()
された直後」「直前のparse()
が失敗した時」または「自身がフォールバックノードである場合」です。
実在する要素ならtrue
、そうでないならfalse
を返します。
保持している値の種類が配列の時、no < size()
かどうかを返します。
配列型ではないならfalse
を返します。
保持している値の種類がオブジェクトの時、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::json
とctl::u8json
とは入力文字列を解釈しません。たとえUTF-8として不正な並びがあっても素通しします。
一方ctl::wjson
とctl::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
型で返されます。
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)
を呼び出しています。
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 &)
が文字列のアンエスケープ処理を行うのに対して、こちらは行わないという違いがありました。
保持している値の種類が配列 (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
という添え字の要素が存在するなら、その要素への参照を返します。
それ以外の時はフォールバックノードへの参照を返します。
保持している値の種類が配列なら、(*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 ~
を定義しておくことにより変更することも出来ます。