Ujson
µjson is a a small, C++11, UTF-8, JSON library
Install / Use
/learn @awangk/UjsonREADME
µjson
About
µjson is a a small, C++11, UTF-8, JSON library.
Its highlights are:
- Small library with very simple API
- Outputs nicely formatted JSON
- Fast UTF-8 conformant parser
- Liberal license
Dependencies
The library uses the double-conversion library from the V8 JavaScript engine for portable conversion between ASCII and floating point numbers. An amalgamation of v1.1.5 of this library is included in the source distribution.
Unit tests are written using Catch. Catch is also included in the source distribution.
The scanner is generated using re2c. The source distribution includes the generated file, so this tool is only needed if you intend to modify the scanner.
Licenses
µjson is licensed under the MIT license. See the LICENSE.md file in the
source distribution.
The dependencies all have liberal software licenses. See
LICENSE-3RD-PARTY.md for the details.
Installation
The library, examples, and unit tests can be built using
CMake. The CMake scripts will automatically
download re2c.
When using the library in another project, rather than using CMake, it may be easier to simply include the four source files,
ujson.hpp
ujson.cpp
double-conversion.h
double-conversion.cc,
directly in the project.
Tutorial
Consider representing books defined using this simple struct as JSON:
struct book_t {
std::string title;
std::vector<std::string> authors;
int year;
};
The first step is to write a small function for converting a book into a
ujson::value:
ujson::value to_json(book_t const &b) {
return ujson::object{ { "title", b.title },
{ "authors", b.authors },
{ "year", b.year } };
}
Using the above function an array of books can be converted to JSON as follows:
book_t book1{ "Elements of Programming",
2009,
{ "Alexander A. Stepanov", "Paul McJones" } };
book_t book2{ "The C++ Programming Language, 4th Edition",
2013,
{ "Bjarne Stroustrup" } };
std::vector<book_t> book_list{ book1, book2 };
ujson::value value{ book_list };
std::string json = to_string(value);
std::cout << json << std::endl;
The last line will print:
[
{
"authors" : [
"Alexander A. Stepanov",
"Paul McJones"
],
"title" : "Elements of Programming",
"year" : 2009
},
{
"authors" : [
"Bjarne Stroustrup"
],
"title" : "The C++ Programming Language, 4th Edition",
"year" : 2013
}
]
Reconstructing the list of books is done by first parsing the JSON
string into a ujson::value:
ujson::value new_value = ujson::parse(json);
assert(new_value == value);
Each element in this array is then converted to a book_t:
std::vector<ujson::value> array = array_cast(std::move(new_value));
std::vector<book_t> new_book_list;
new_book_list.reserve(array.size());
for (auto it = array.begin(); it != array.end(); ++it)
new_book_list.push_back(make_book(std::move(*it)));
assert(new_book_list == book_list);
The helper function make_book is implemented as follows:
book_t make_book(ujson::value v) {
if (!v.is_object())
throw std::invalid_argument("object expected for make_book");
book_t book;
std::vector<std::pair<std::string, ujson::value>> object =
object_cast(std::move(v));
auto it = find(object, "title");
if (it == object.end() || !it->second.is_string())
throw std::invalid_argument("'title' with type string not found");
book.title = string_cast(std::move(it->second));
it = find(object, "authors");
if (it == object.end() || !it->second.is_array())
throw std::invalid_argument("'authors' with type array not found");
std::vector<ujson::value> array = array_cast(std::move(it->second));
book.authors.reserve(array.size());
for (auto it = array.begin(); it != array.end(); ++it) {
if (!it->is_string())
throw std::invalid_argument("'authors' must be array of strings");
book.authors.push_back(string_cast(std::move(*it)));
}
it = find(object, "year");
if (it == object.end() || !it->second.is_number())
throw std::invalid_argument("'year' with type number not found");
book.year = int32_cast(it->second);
return book;
}
Reference
A JSON value must be null, a boolean, a number, a string, an array, or
an object (see RFC7159). In
µjson the class ujson::value is used to represent all of these six
types.
The actual type of a value can queried using ujson::value::type or
using one of the convenience methods, such as ujson::value::is_null.
Values always contain one of the six possible types (ujson::value
does not have a special uninitialized state).
The class ujson::value is a proper immutable value. Therefore, once
a value has been created, it cannot be changed, though of course it
can be assigned a new value. Values can be compared for equality and
inequality.
Casts are used to extract the embedded type again. For instance
bool_cast is used to extract the bool from values with boolean
types. If the value is cast to a wrong type a bad_cast exception is
thrown.
Null
Default constructed values are null:
ujson::value null_value; // null
A constant null value is defined in the ujson namespace.
assert(ujson::null == null_value);
Values support stream i/o:
std::cout << null_value << std::endl; // prints 'null'
Booleans
Values can be initialized with and assigned bools:
ujson::value boolean(true);
assert(bool_cast(boolean) == true);
std::cout << boolean << std::endl; // prints 'true'
boolean = false;
assert(bool_cast(boolean) == false);
std::cout << boolean << std::endl; // prints 'false'
Numbers
Inside ujson::values numbers are represented as 64-bit doubles:
ujson::value number = M_PI;
std::cout << number << std::endl; // prints '3.141592653589793'
The double value can be extracted using a double_cast:
double d = double_cast(number); // d == M_PI
The double-conversion library is used instead of the platform specific C runtime library to ensure lossless and portable roundtripping of doubles from ASCII to binary.
Beware that only finite numbers are valid in JSON. Infinities and NaNs are not allowed:
number = std::numeric_limits<double>::infinity(); // throws bad_number
Numbers can also represent signed 32-bit integers:
number = 1024;
std::cout << number << std::endl; // prints '1024'
The integer value can be extracted using an int32_cast:
std::int32_t i = int32_cast(number); // i == 1024
Unsigned 32-bit integers are also supported.
Strings
Strings are stored internally as UTF-8:
ujson::value value = "\xC2\xA9 ujson 2014"; // copyright symbol
If the string is not zero-terminated or contains embedded zeros, the length must be passed too:
char title[]= { 0xC2, 0xB5, 'j', 's', 'o', 'n' }; // micro sign + json
value = ujson::value(title, 6);
Strings passed to to µjson must be valid UTF-8:
value = "\xF5"; // invalid utf-8; throws bad_string
If the string is known to be valid UTF-8, the validation step can be skipped by passing no in the last argument of the constructor:
value = ujson::value("valid", 5, ujson::validate_utf8::no);
Strings can also be constructed from std::strings:
std::string string("ujson");
value = string; // copy into value
Alternatively, if the original string is no longer needed, the
std::string can be moved into the value and the copy avoided:
value = std::move(string); // move into value
Strings can be accessed using the two string_cast methods. The first
accepts l-values and returns a ujson::string_view object:
auto view = string_cast(value);
std::cout << view.c_str() << std::endl; // prints 'ujson'
The returned string view object provides read-only access to the contained string.
The second string cast method accepts r-values and can be used to move a string out of a value:
string = string_cast(std::move(value)); // move string out of value
assert(value.is_null());
Moved from values are always null.
See the "Implementation Details" section for more information on how
µjson handles std::strings implemented using reference counting
versus short string optimization.
Arrays
Arrays are represented using ujson::array, which is simply a typedef for
std::vector<ujson::value>:
auto array = ujson::array{ true, M_PI, "a string" };
ujson::value value(array);
Copying the array can be avoided by moving it into the value:
value = std::move(array);
Read-only access to the contained array is possible using array_cast:
ujson::array const &ref = array_cast(value);
The original array can be recovered by moving the array out of the value:
array = array_cast(std::move(value));
As shown in the tutorial it is also possible to use a std::vector<T>
of types T implicitly convertable to ujson::value or a vector of
types that supply a to_json function.
ujson::values are designed to be cheap to copy. Internally, strings,
arrays, and objects, are stored using std::shared_ptr<>s, so copying
only requires incrementing a reference count. However, this sharing
has implications for when it is possible to move:
ujson::value value1 = std::move(array);
ujson::value value2 = value1; // value2 shares immutable array with value1
auto tmp1 = array_cast(std::move(value1)); // note: copy!
auto tmp2 = array_cast(std::move(value2)); // move
In short, moves are only possible if the value
