ctl::json is a header-only C++ library for manipulating JSON data.
ctl::json is a C++ json library. Features are as follows:
The oldest compiler on where I confirm ctl::json can be used is Visual C++ 2005 in Visual Studio 2005.
Place ctljson.hpp
somewhere in your PATH and include it.
Create an instance of the ctl::json
type, and pass a JSON data string (std::string
or null-terminated string) to its member function, ctl::json::parse()
:
// Example 01: ctl::json root; const std::string jsondata = R"({ "a": 1, "b": { "b1": "#", "b2": "%" } })"; root.parse(jsondata); // Reads and parses JSON data. printf(R"(["a"] = %d, ["b"]["b1"] = "%s")" "\n", root["a"].num<int>(), root["b"]["b1"].str().c_str()); // Output: // ["a"] = 1, ["b"]["b1"] = "#"
Reading can be performed also by passing a JSON data string to the constructor:
// 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()); // Output: // ["c"] = 2, ["d"]["d2"] = "%%"
The member function stringify()
or to_string()
of the ctl::json
type returns an instance of std::string
that contains a JSON data.
There is no difference in behaviour between these two member functions, only the names are different. The former is a JavaScript-derived name, and the latter is a C++-ish name.
// Example 03: ctl::json root, childnode; childnode(0) = "zero"; childnode(1) = "one"; root("obj") = childnode; root("str") = "ABC"; const std::string jsondata = root.stringify(); // Outputs JSON data. // const auto jsondata = root.stringify<std::string>(); // Returned type can be specified explicitly by template argument. printf("%s\n", jsondata.c_str()); // Output: // {"obj":["zero","one"],"str":"ABC"}
This library provides the four types, ctl::json
, ctl::u8json
, ctl::wjson
, ctl::u16json
.
Their usage is basically the same, except that the first two types hold a string as UTF-8 and the other two hold as UTF-16.
On behalf of them, how to use ctl::json
is explained in the following.
An instance of ctl::json
can store a value of one of the types defined in RFC 8259, number, string, array, object (name-keyed array), or three literal names, true
, false
, or null
.
Each element in an array or object is also an instance of ctl::json
.
So, for example, an array js
's first element's second element can be accessed by js[1][2]
.
The member function type()
can be used to check which type an instance currently stores.
The return value of this function is an integer of enum type ctl::json::value_type
, consisting of the following constant values:
number
, string
, array
, object
, boolean
(only true
or false
can be stored), null
, unassigned
, fallback
.
Writing data of a type different from the type a target instance currently stores does not cause an error, but simply the instance's type information will be overwritten accordingly.
For arrays and objects, there are two ways to access their elements:
operator()()
:
Always returns a reference to an actually existing element. For an array, if an index value n
passed as an argument is equal to or greater than the array size, first the size of the array is expanded to n+1
then returnes a reference to the element at [n]
; for an object, if a key name passed as an argument does not in the object, first a new element whose name is n
is created then returns a reference to it.
operator[]()
:
If the element specified by the index value or key name already exists, returns a reference to it.
If it does not exist, returns a reference to the special element, "the fallback node", without creating any new element.
This access way guarantees not to change the current tree structure of JSON data.
The fallback node is a special node that is returned when the element specified by the index value or key name does not exist.
The fallback node is returned only when operator[]()
is used to access a value.
// 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>()); // Accessing ["c"] and ("d") that do not exist in root. const std::string jsondata = root.stringify(); // Exports JSON data. printf("%s\n", jsondata.c_str()); // Output: // root["c"] = 0, root("d") = 0 // {"a":1,"b":2,"d":null}
In Example 04 above, access to root["c"]
using operator[]()
did not cause creation of any new element, whereas access to root("d")
using operator()()
caused creation of a new element (The reason why its value is null
is because no assignment has been done for the newly created element).
Whether an element is an actually existing element or the fallback node can be checked by calling member function exists()
(which returns true
if a real element) or is_fallback()
(which returns true
if the fallback node).
// Example 05: ctl::json root(R"({ "a": 1 })"); printf(R"(["a"] = %d/%d)" "\n", root["a"].exists(), // Can be written also as root.exists("a") root["a"].is_fallback()); printf(R"(["b"] = %d/%d)" "\n", root["b"].exists(), root["b"].is_fallback()); // Output: // ["a"] = 1/0 // ["b"] = 0/1
All descendants of the fallback node are fallback nodes.
operator[]()
allows you to access data in a deep tree without worrying that the tree structure will be changed inadvertently.
// 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()); // Output: // ["a"]["b"]["c"] = 0/1
It is not possible to assign a value to the fallback node.
Making it an lvalue does not result in an error, but it is simply ignored.
And also, trying creation of a new element by using operator()()
beneath the fallback node will be ignored.
// Example 07: ctl::json root(R"({ "a": 1 })"); root["a"]["b"]("c") = 2; // Trying creating ("c") under non-existing ["b"] printf(R"(["a"]["b"]("c") = %d/%d %d)" "\n", root["a"]["b"]("c").exists(), root["a"]["b"]("c").num<int>(), root["a"].num<int>()); // Output: // ["a"]["b"]("c") = 0/0 1 // The value of ["a"] remains 1. Not changed to the object type.
The full list of member functions of ctl::json
, ctl::wjson
, ctl::u16json
, and ctl::u8json
class.
The following types are defined.
char_type | string_type | sview_type | Note | |
---|---|---|---|---|
ctl::json | typedef of char |
typedef of std::u8string |
std::string_view -like. |
|
ctl::u8json | typedef of std::string |
typedef of char8_t |
std::u8string_view -like. |
Only C++20 and later. |
ctl::wjson | typedef wchar_t |
typedef std::wstring |
std::wstring_view -like. |
Defined only if 0xffff <= WCHAR_MAX < 0x110000. |
ctl::u16json | typedef of char16_t |
typedef of std::u16string |
std::u16string_view -like. |
Only C++11 and later. |
sview_type
is a similar class to std::string_view
introduced in C++17. It can handle both a string container of the std::string
family and a pointer to a null-terminated string. Because it is not std::string_view
itself, it can be used with pre-C++17 compilers.
sview_type
s in ctl::json
and ctl::u8json
are used to pass a UTF-8 sequence.
sview_type
s in ctl::wjson
and ctl::u16json
are used to pass a UTF-16 sequence.
As mentioned in the table above, ctl::wjson
is defined only if WCHAR_MAX
is equal to or more than 0xFFFF and less than 0x110000.
Virtually, it is provided for Windows.
The folowing values are defined to represent the type of data stored by an instance:
enum value_type { // Since version 2.109. array, object, number, string, boolean, null, unassigned, fallback // Until version 2.108. null, boolean, number, string, array, object, unassigned, fallback };
The return value of member function type() is this type.
boolean
is a type that can store a true
or false
value.
The last two types are defined for internal use.
An instance created with the constructor that takes no argument is of the unassigned
type.
There are six types as public
ones.
json(); | (1) |
json(const json &right); | (2) |
template <typename StringType> explicit json(const StringType &s); |
(3) |
template <typename StringType> json(const StringType &s, json_error &e); |
(4) (Since 2.112) |
template <typename Iterator> json(Iterator begin, Iterator end); |
(5) |
template <typename Iterator> json(Iterator begin, Iterator end, json_error &e); |
(6) (Since 2.112) |
In addition, there exists the seventh one as a private
member for creating the fallback node.
(3) calls parse(s).
(4) calls parse(s, e).
(5) calls parse(begin, end).
(6) calls parse(begin, end, e).
Only (3) and (5) call clear()
if an error occurs during parsing (because otherwise there is no way to inform about the occurrence of the error).
Since version 2.106, the parameter type of (3) is not string_type
, so any string type can be passed to it.
template <typename StringType> bool parse(const StringType &s) |
(1) |
template <typename StringType> bool parse(const StringType &s, json_error &e) |
(2) (Since 2.112) |
template <typename CharType> bool parse(const CharType *p) |
(3) |
template <typename CharType> bool parse(const CharType *p, json_error &e) |
(4) (Since 2.112) |
template <typename Iterator> bool parse(Iterator begin, Iterator end) |
(5) |
template <typename Iterator> bool parse(Iterator begin, Iterator end, json_error &e) |
(6) (Since 2.112) |
These parse (1)(2) string s
of an arbitrary string container type StringType
(std::basic_string
itself or its API compatible class), (3)(4) pointer p
to a null-terminated string of an arbitrary character type CharType
, and (5)(6) character sequence [begin, end)
, as JSON data encoded in UTF-8.
true
is returned if parsing has successfully completed its run.
When any error occurs during parsing, false
is returned. In this case, (2)(4)(6) stores the reason of the error in e
before returning.
During parsing, all escaped characters (such as \" \t \n) in the string are unescaped.
Except for this point, the parsers of ctl::json
and ctl::u8json
do not interpret the input string. Even an input string contains a sequence being invalid as UTF-8, it is stored as is.
The parsers of ctl::wjson
and ctl::u16json
break off parsing if any sequence that cannot be converted to UTF-16 is found.
Although Iterator
in (5) and (6) supports forward iterator and above, it is recommended to use random access iterator or above because when an error occurs std::distance()
is used to calculate the position at where the error occurs.
Since version 2.106, the parameter type of (1)(3) has been changed from string_type
, char_type
respectively, so any string type can be passed to them.
Up to version 2.111, if a parsing error occurs clear()
was called, whereas since version 2.112 it is not called and data that have been read in successfully up to the error point are kept in the instance.
BreakingChange
Up to version 2.111, (5) that takes a pair of iterators as parameters returned the iterator at the point when parsing is finished or stopped.
Since version 2.112, as error information can be received via json_error &e
, (5) simply returns an boolean value indicating the parsing has succeeded or not.
string_type stringify() const | (1) |
string_type to_string() const | (2) |
template <typename StringType> StringType stringify() const |
(3) (Since 2.106) |
template <typename StringType> StringType to_string() const |
(4) (Since 2.106) |
template <typename StringType> void stringify(StringType &out) const |
(5) (Since 2.112) |
template <typename StringType> void to_string(StringType &out) const |
(6) (Since 2.112) |
These export the data stored in the instance as a JSON string encoded in UTF-8,
(1)(2)(3)(4) return it as the string, and (5)(6) output the string to out
.
During exporting, characters that cannot be output as is, such as "
and \
, are escaped.
There is no difference in behaviour between stringify()
and to_string()
, only the names are different. The former is a JavaScript-derived name, and the latter is a C++-ish name.
The fallback node and instances with type json::unassigned
are all translated to null
.
Since version 2.106 the return type can be specified by the first template argument. If omitted, a string of string_type
is returned..
Clears the current value and sets the type of the instance to unassigned
.
Returns the value type. It is an integer of enum value_type.
Returns true
if the stored value is of the number type; otherwise returns false
.
Returns true
if the stored value is of the string type; otherwise returns false
.
Returns true
if the stored value is of the boolean type; otherwise returns false
.
Returns true
if the stored value is of the boolean type and true
; otherwise returns false
.
Returns true
if the stored value is of the boolean type and false
; otherwise reutrns false
.
Returns true
if the stored value is null
.
Returns true
if the instance has an array; otherwise returns false
.
Returns true
if the instance has an object; otherwise returns false
.
Returns true
if the instance is the fallback node; otherwise returns false
.
Returns true
if the instance has any valid value; otherwise returns false
.
false
is returned when
1) nothing has been assigned since the instance was created,
2) clear()
is called,
3) the previous parsing has failed, or
4) the instances is the fallback node.
bool exists() const | (1) |
bool exists(const std::size_t no) const | (2) |
bool exists(const sview_type key) const | (3) |
(1) Returns true
if the instance is not the fallback node but a real element; otherwise returns false
.
(2) When the instance is of the array type and no < size()
, returns true
.
Otherwise returns false
.
(3) When the instance is of the object type and the element whose index name is key
exists, returns true
.
Otherwise returns false
.
json &operator=(const json &right) | (1) |
json &operator=(double right) | (2) |
json &operator=(bool right) | (3) |
json &operator=(const sview_type right) | (4) |
json &operator=(const char_type *right) | (5) |
json &operator=(const psnum &right) | (6) (Since 2.115) |
json &operator=(value_type right) | (7) (Since 2.115) |
These copy rvalue right
to self (lvalue).
If the current type of the instance is different than the type of the given parameter, the type kind is also changed accordingly.
(2) is called when the parameter is not only of the double
type but also of any integer type. Calls set_num(right, -6)
.
(3) is called when the parameter is a boolean value, true
or false
. Calls set_bool(right)
.
(4) is called when the parameter is of any string container type, such as std::string
and std::string_view
. Calls set_str(right)
.
(5) is called when the parameter is a pointer to a string. Calls set_str(right)
.
(6) is called when the parameter is of the psnum type. Calls set_num(right.value, right.precision)
.
(7) initialises myself (lvalue) depending on the value of right
.
If it is ctl::json::null
then calls set_null()
.
If ctl::json::array
then calls empty_array()
, if ctl::json::object
then calls empty_object()
, respectively.
(2)(6) When assigning with a numeric value, the numeric value is stored along with a string representation that has been converted from the value as if a printf
family function is called with arguments ("%.6f", value)
. If the numeric value is a decimal, trailing zeros after the decimal point are removed from the resulting string, and when there are no digits are left after the decimal point, the decimal point itself is also removed.
For example, 1.23456789
and 0.0
are stringified as "1.234568"
and "0"
respectively and stored in the instance.
This "stringified numeric value" is used when the value is output by member function stringify()
or to_string
to a JSON text file.
For changing the rounding precision after the decimal point and/or keeping trailing zeros, you can use member function set_num()
or (6).
ECMAScript (JavaScript) stores numbers in the IEEE 754 64-bit format.
So, this library also stores a numerical value in the double
type.
As the mantissa part of IEEE 754 64-bit format is 53 bits, trying to read or write a interger with a greater number of more than 53 bits would cause a precision error. This is a specification originated from ECMAScript.
Furthermore, precision errors may also occur if your compiler's double
type is not based on IEEE 754.
In situations where strict precision is required, it is recommended to read and write a number as a string (such as js = "12.34"
instead of js = 12.34
).
BreakingChange Until 2.102, when assigning from a string, all escaped characters (\" \t \n etc.) in the string are unescaped during copying.
double num() const | (1) |
template <typename NumType> NumType num() const |
(2) (Since 2.108) |
When type() == json::number
, returns the stored value as a numeric value.
When type() == json::boolean
and the stored value is true
, then returns 1.0
.
Otherwise returns 0.0
.
Since version 2.108 the return type can be specified by the template argument. If omitted, a value of the double
type is returned.
For example, js.num()
returns a value of the double
type, whereas js.num<int>()
returns a value having been converted to the int
type.
string_type numstr() const | (1) |
template <typename StringType> void numstr(StringType &out) const |
(2) (Since 2.112) |
(1) When type() == json::number
, returns the stored value as a string.
When type() == json::boolean
, if the stored value is true
returns the literal string "true"
, otherwise returns "false"
.
When type() == json::null
returns the string "null"
.
When type()
is a type other than above, returns an empty string.
(2) When the instance holds any type of number
, boolean
, or null
, then does the same conversion as (1) and outputs it to out
.
Otherwise, returns after clearing out
.
For all data types to be converted to a string, you can use stringify()
/ to_string()
above.
string_type str() const | (1) |
template <typename StringType> void str(StringType &out) const |
(2) (Since 2.112) |
(1) When type() == json::string
returns the stored string.
Othewise return an empty string.
(2) When type() == json::string
,
outputs the stored string to out
then returns.
Othewise returns after clearing out
.
When precision
is a positive number, conversion as if a printf
family function is called with arguments ("%.*f", precision, d)
is performed and both the resulting string and the original numeric value d
are stored in the instance.
When precision
is a negative number, the precision
value is first converted to an absolute value and the conversion described above is performed. Then, in addition, if the value d
is a decimal, trailing zeros after the decimal point are removed, and when there are no digits are left after the decimal point, the point itself is also removed:
set_num(0.123, 6)
-> "0.123000"
set_num(0.123, -6)
-> "0.123"
set_num(0.0, 6)
-> "0.000000"
set_num(0.0, -6)
-> "0"
This "stringified numeric value" is used when the value is output by member function stringify()
or to_string
to a JSON text file.
Incidentally, operator=(double d)
above calls set_num(d, -6)
.
Using the fact that an instance of the number type holds both a numeric value and its string representation, conversion between a number and a string can be done independently of JSON:
// From string to number. double dbl = ctl::json("1234.56789").num(); // dbl = 1234.567890; // From number to string. std::string str = ctl::json().set_num(1.234, 4).numstr(); // str = "1.2340";
json &set_str(const sview_type s) | (1) |
template <typename Iterator> json &set_str(Iterator begin, const Iterator end) |
(2) |
These copy string s
, null-terminated string p
, or character sequence [begin, end)
, to self (lvalue), with treating as a UTF-8 string.
The overload function that takes const char_type *
as a parameter was removed in version 2.112 because it has become unnecessary by changing the parameter type of (1) from string_type to sview_type.
BreakingChange
Until 2.102, the name of these functions was raw()
. Unlike operator=(const string_type &)
that unescaped characters in the string during copying, these functions did not unescape.
When the instance is of the array (json::array
) or object (json::object
) type, returns the current number of elements.
Otherwise returns 0.
const json &operator[](const std::size_t no) const | (1) |
json &operator[](const std::size_t no) | (2) |
const json &operator[](const sview_type key) const | (3) |
json &operator[](const sview_type key) | (4) |
(1)(2) If the instance is of the array and no < size()
,
returns a reference to (*this)[no]
.
Otherwise returns a reference to the fallback node.
(3)(4) If the instance is of the object type and the element whose index name is key
exists, returns a reference to it.
Otherwise returns a reference to the fallback node.
json &operator()(const std::size_t no) | (1) |
json &operator()(const sview_type key) | (2) |
(1) If the instance is of the array type, returns a reference to (*this)[no]
.
When no >= size()
, the size of the array is first expanded to no + 1
and returns a reference to (*this)[no]
.
If the current stored value is not of the array type, the above process is performed after the type is changed to the array type.
(2) If the instance is of the object type, returns a reference to (*this)[key]
.
When an element whose index name is key
does not exist, it is created and returns a reference to it. And the name key
is added to the object's internal order list.
If the current stored value is not of the object type, the above process is performed after the type is changed to the object type.
void erase(const std::size_t no) | (1) |
void erase(const sview_type key) | (2) |
(1) If the instance is of the array type and no < size()
, removes (*this)[no]
.
(2) If the instance is of the object type, removes (*this)[key]
.
template <typename ValueType> void insert(const std::size_t no, const ValueType &right) |
(1) |
template <typename ValueType> void insert(const sview_type pos, const sview_type key, const ValueType &right) |
(2) |
(1) If the instance is of the array type and no <= size()
, insert right
jsut before (*this)[no]
.
(2) If the instance is of the object type, creates a new element whose index name is key
just before (*this)[pos]
and copies right
to it.
If an element whose index name is key
already exists in the object, it is first removed and the above preocess is performed.
When an element whose index name is pos
does not exist, no operation is performed.
(1)(2) Any type that can be passed to operator=()
is accepted as ValueType
, the type of the inserted element.
(1)(2) Until version 2.114, the type of last parameter was json
.
template <typename ValueType> void push_back(const ValueType &right) |
(1) |
template <typename ValueType> void push_back(const sview_type key, const ValueType &right) |
(2) |
(1) If the instance is of the array type, adds right
after the end of the array.
(2) If the instance is of the object type, adds right
with the index name key
as the last element of the object.
If any element whose name is key
already exists, the element is replaced with right
and its order is moved to the last.
(1)(2) Any type that can be passed to operator=()
is accepted as ValueType
, the type of the push-backed element.
ctl::json js("[]"); // Makes an array. js.push_back(1); js.push_back("a"); js.push_back(js); js.push_back(true); js.push_back(false); js.push_back(ctl::json::null); js.push_back(ctl::json::array); js.push_back(ctl::json::object); js.push_back(ctl::json::psnum(1.2, 4)); // js.stringify() == [1,"a",[1,"a"],true,false,null,[],{},1.2000]
For adding a new element to an object, using of assigment via operator()() is recommended rather than using this function; because this push_back()
is a bit slow for ensuring that the element whose index name is key
is placed at the last in the object's internal order list.
ctl::json js; js("a") = 1; js("b") = 2; js("c") = 3; js("b") = 4; // Reassignment of the element named "b". printf("%s\n", js.to_string().c_str()); // {"a":1,"b":4,"c":3} // Output in the creation order of elements. js.push_back("b", 5); // Reassignment of the element named "b". printf("%s\n", js.to_string().c_str()); // {"a":1,"c":3,"b":5} // Element "b" has been moved to the last.
(1)(2) Until version 2.114, the type of last parameter was json
.
json allkeys() const | (1) (Since 2.113) |
void allkeys(json &out) const | (2) (Since 2.113) |
(1) If the instance is of the object type, returns an JSON array whose elements are string of all key names; otherwise, return an empty json
instance.
(2) If the instance is of the object type, outputs to out
an JSON array whose elements are string of all key names; otherwise clears out
.
ctl::json js; js("ab") = 1; js("cd") = 2; js("ef") = 3; ctl::json keylist = js.allkeys(); // keylist.type() == json::array // keylist.size() == 3 // keylist[0].str() == "ab" // keylist[1].str() == "cd" // keylist[2].str() == "ef" std::string json = keylist.stringify(); // ["ab","cd","ef"]
Makes the content of the instance an empty array.
js.empty_array()
is equivalent to js.parse("[]");
.
Makes the content of the instance an empty object.
js.empty_object()
is equivalent to js.parse("{}");
.
Set the type of the instance to boolean
, and the value to b
.
Set the type and value of the instance to null
.
psnum
(Precision-Specified-NUMber) is a helper class for a numeric value to be copied by operator=()
with specifying a precision instead of set_num()
.
This class has the following two member variables and one constructor only:
double value;
int precision;
psnum(double d, int p) : value(d), precision(p) {}
Usage:
ctl::json js;
js = ctl::json::psnum(0.123, 6);
// js.stringify() == "0.123000"
By passing an instance of the json_error
type to parse()
as an argument,
you can receive the information of the position and reason of an error encountered during parsing.
This json_error
class has the following member functions:
ctl::json_error::code_type code()
std::size_t position()
The folowing values are defined as code_type
:
code_type | Meaning |
---|---|
ec_ok
ec_no_error |
No error. These values are 0. |
ec_array | Error during parsing an array. No closing ']' found. |
ec_object | Error during parsing an object. No closing '}' found. |
ec_object_name | Error during parsing an object name. |
ec_no_colon | No ':' after an object name. |
ec_string | Error during parsing a string. No closing '"' found.(Only ctl::wjson and ctl::u16json )
Invalid UTF-8 sequence that cannot be converted to UTF-16 was found.
|
ec_number | Invalid number representation. |
ec_literal | Unknown literal found. |
ctl::json_error e; ctl::json js; js.parse("123", e); // Return value is true. // Both e.code() and e.position() return 0. js.parse("123E+", e); // Return value is false. // e.code() returns ctl::json_error::ec_number, e.position() returns 5.
There is no exception specific to this library. Only std::bad_alloc
is thrown when new
is failed internally.
The default namespace
of this library is ctl
, but you can change it with any name you like by defining NAMESPACE_CTLJSON
in advance, like #define NAMESPACE_CTLJSON othername
.