SkillAgentSearch skills...

Ujson

µjson is a a small, C++11, UTF-8, JSON library

Install / Use

/learn @awangk/Ujson
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

µ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

View on GitHub
GitHub Stars9
CategoryDevelopment
Updated1y ago
Forks3

Languages

C++

Security Score

75/100

Audited on Jan 10, 2025

No findings