ctl::json

ctl::json is a JSON template library for C++.

Contents

Features

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.

Download

How to Use

Place ctljson.hpp somewhere in your PATH and include it.

Read

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",
    (int)root["a"].num(),
    root["b"]["b1"].str().c_str());

//  Output:
//  ["a"] = 1, ["b"]["b1"] = "#"
		

If std::u8string or u8"" string literal is preferred to std::string or "", you can use ctl::u8json instead of ctl::json.

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",
    (int)root["c"].num(),
    root["d"]["d2"].str().c_str());

//  Output:
//  ["c"] = 2, ["d"]["d2"] = "%%"
		

Write

The member function stringify() or to_string() of the ctl::json type returns an instance of std::string that contains a JSON data. Similarly, ctl::u8json returns a JSON data packed in the std::u8string type.
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.
printf("%s\n", jsondata.c_str());

//  Output:
//  {"obj":["zero","one"],"str":"ABC"}
		

Data types and access

An instance of ctl::json / ctl::u8json 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. And each element in an array or object can also store a value of these types.

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 (or ctl::u8json::value_type for ctl::u8json), consisting of the following constant values: number, string, array, object, boolean (only true or false can be stored), null, unassigned, fallback.

Ways to access to each type are as follows.
Note that the type string_type that appears in the explanation below is a typedef of std::string when the instance is of the ctl::json type, or of std::u8string when the instatnce is of the ctl::u8json type.

Number (ctl::json::number)
Reading double num() const
string_type numstr() const
Writing ctl::json &operator=(double)
ctl::json &set_num(double, int precision)

ECMAScript (JavaScript) stores numbers in the IEEE 754 64-bit format. So, this library also stores numerical values in the double type. To simplify implementation, overload functions for integer types are intentionally not provided.

For a ctl::json js, to get a number as a numerical value, you can write like js.num(). To get a number as a string, you can write like js.numstr().

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 is not safe but 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 numbers as strings.

In writing with operator=(), decimals after the decimal point are rounded to 6 digits, trailing zeros are removed, and if there are no digits are left after the decimal points, the decimal point itself is also removed.
To change the rounding precision after the decimal point and/or to keep trailing zeros, you can use set_num(). This member function is explained later.

String (ctl::json::string)
Reading string_type str() const
Writing 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)

Whether ctl::json or ctl::u8json, passed strings are interpreted as UTF-8.

Overload function operator=(const char_type *) is intentionally not provided. This is because if it exists, when 0 is specified as a argument an overload ambiguity will occur between it and operator=(double).

[Until 2.102] The name of set_str() was raw(). In assignment by operator=(), characters in the copied string is all unescaped, while in assignment by raw(), the passed string becomes the instance value as is. For example, while js = "\\u0026" sets js's value to "&", js.raw("\\u0026") sets the value to "\u0026"

true, false (ctl::json::boolean)
Reading bool is_true() const
bool is_false() const
string_type numstr() const
Writing ctl::json &set_bool(const bool)

is_true() returns true when the instance is of the boolean type and its value is true. is_false() returns true when the instance is of the boolean type and its value is false.
numstr() returns the instance's value as a string "true" or "false" if the instance is of the boolean type.

Overload function operator=(const bool) is intentionally not provided. This is because if it exists, when an integer value is passed as a argument an overload ambiguity will occur between it and operator=(double).

null (ctl::json::null)
Reading bool is_null() const
string_type numstr() const
Writing ctl::json &set_null()

is_null() returns true if the instance is null.
numstr() returns a string "null" if the instance is null.

Array (ctl::json::array)
Reading ctl::json &operator[](const std::size_t pos)
ctl::json &operator()(const std::size_t pos)
Writing ctl::json &operator[](const std::size_t pos)
ctl::json &operator()(const std::size_t pos)
Addition void push_back(const ctl::json &newnode)
Insert bool insert(const std::size_t pos, ctl::json &newnode)
Remove bool erase(const std::size_t pos)
Get number of elements std::size_t size() const

A value of each element in an array is itself an instance of ctl::json (or ctl::u8json). Therefore, for example, "the second data of the first element" can be accessed with js[1][2].

The difference between writing like js[0] and writing like js(0) is explained later.

Object (ctl::json::object)
Reading ctl::json &operator[](const string_type &key)
ctl::json &operator()(const string_type &key)
Writing ctl::json &operator[](const string_type &key)
ctl::json &operator()(const string_type &key)
Addition void push_back(const ctl::json &newnode)
Insert bool insert(const string_type &pos, const string_type &key, ctl::json &newnode)
Remove bool erase(const string_type &key)
Get number of elements std::size_t size() const

While the array type uses a numeric value as an index, the object type uses a string as an index key. Depending on the programming language, this type is also called a "associative array" or "dictionary".
It is the same as the array type except that the index is a string.

The difference between writing like js["key"] and js("key") is explained later.

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.

Two access ways

For arrays and objects, there are two ways to access their elements.

Fallback node

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",
    (int)root["c"].num(),
    (int)root("d").num());
    //  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(),
    (int)root["a"]["b"]("c").num(),
    (int)root["a"].num());

//  Output:
//  ["a"]["b"]("c") = 0/0 1
//  The value of ["a"] remains 1. Not changed to the object type.
			

API

The full list of member functions of ctl::json / ctl::u8json class.

ctl::jsonnode

The base class from which ctl::json and ctl::u8json have been derived. Defined as follows:

#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
		

The reason why template parameters are complicated is because it is intended to make it possible to use custom containers. For usual use, two typedefs, ctl::json and ctl::u8json are provided.

Because it is not assumed to use this base class ctl::jsonnode directly, instead ctl::json is used for explanation in the following sections.

Types

string_type and char_type

The following types are defined.

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

Constant values

enum value_type

The folowing values are defined to represent the type of data stored by an instance:

enum value_type
{
    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.

Member functions

Constructor

There are three types as public ones: 1) creates an empty instance, 2) copies from another instance, and 3) receives and parses a string.

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

In addition, there exists the fourth one as a private member for creating the fallback node.

void clear()

Clears the current value and sets the type of the instance to unassigned.

value_type type() const

Returns the value type. It is an integer of enum value_type.

bool is_num() const

Returns true if the stored value is of the number type; otherwise returns false.

bool is_str() const

Returns true if the stored value is of the string type; otherwise returns false.

bool is_bool() const

Returns true if the stored value is of the boolean type; otherwise returns false.

bool is_true() const

Returns true if the stored value is of the boolean type and true; otherwise returns false.

bool is_false() const

Returns true if the stored value is of the boolean type and false; otherwise reutrns false.

bool is_null() const

Returns true if the stored value is null.

bool is_array() const

Returns true if the instance has an array; otherwise returns false.

bool is_object() const

Returns true if the instance has an object; otherwise returns false.

bool is_fallback() const

Returns true if the instance is the fallback node; otherwise returns false.

bool exists() const

Returns true if the instance is not the fallback node but a real element; otherwise returns false.

bool exists(const std::size_t no) const

When the instance is of the array type and no < size(), returns true. Otherwise returns false.

bool exists(const string_type &key) const

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

These copy rvalue right to self (lvalue).

When assigning from 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 points, the decimal point itself is also removed.
This "stringified numeric value" is used when the value is output by member function stringify() or to_string.

For changing the rounding precision after the decimal point and/or keeping trailing zeros, you can use member function set_num().

[Until 2.102] When assigning from a string, all escaped characters (\" \t \n etc.) in the string are unescaped during copying.

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

These parse string s, null-terminated string p, or character sequence [begin, end), as JSON data encoded in UTF-8.
The first two will return true if parsing has successfully completed its run. The last one that takes a pair of iterators as parameters will return the iterator at the point when parsing is finished or stopped. If this returned iterator is equal to end, it means that the parsing has been successfully completed.

When parsing, all escaped characters (such as \" \t \n) in the string are unescaped.

string_type stringify() const
string_type to_string() const

These output the value of the instance as JSON data encoded in UTF-8. During exporting, characters that cannot be output as is, such as " and \, are escaped.

There is no difference in behaviour between these two functions, 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.

double num() const

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.

string_type numstr() const

When type() == json::number, the stored value is returned as a string.
When type() == json::boolean, if the stored value is true returns literal string "true", otherwise returns string literal "false".
When type() == json::null returns literal string "null".

When type() is not any type above, returns an empty string.

For all data types to be converted to a string, you can use stringify() / to_string() above.

string_type str() const

When type() == json::string returns the stored string. Othewise return an empty string.

json &set_num(double d, int precision)

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 points, the decimal point itself is also removed.

This "stringified numeric value" is used when the value is output by member function stringify() or to_string.

Incidentally, operator=(double d) above calls 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)

These copy string s, null-terminated string p, or character sequence [begin, end), to self (lvalue), with treating as a UTF-8 string.

[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.

std::size_t size() const

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
json &operator[](const std::size_t no)

If the instance is of the array and no < size(), returns a reference to (*this)[no]. Otherwise returns a reference to the fallback node.

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

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)

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.

json &operator()(const string_type &key)

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)

If the instance is of the array type and no < size(), removes (*this)[no].

void erase(const string_type &key)

If the instance is of the object type, removes (*this)[key].

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

If the instance is of the array type and no <= size(), insert right jsut before (*this)[no].

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

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.

void push_back(const json &right)

If the instance is of the array type, adds right after the end of the array.

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

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.

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.

json &set_bool(const bool b)

Set the type of the instance to boolean, and the value to b.

json &set_null()

Set the type and value of the instance to null.

Exception

There is no exception specific to this library. Only std::bad_alloc is thrown when new is failed internally.

Namespace

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.