An easy-to-use and competitively fast JSON parsing library for C++17, forked from Bitcoin Cash Node's own UniValue library.
Supports parsing and serializing, as well as modeling a JSON document. The central class is UniValue
, a universal value class, with JSON encoding and decoding methods. UniValue
is an abstract data type that may be a null, boolean, string, number, array container, or a key/value dictionary container, nested to an arbitrary depth. This class implements the JSON standard, RFC 8259.
The below is taken from basic_example.cpp.
// An example of how to build an object
UniValue uv;
auto &obj = uv.setObject(); // this clears the uv instance and sets it to type VOBJ, returning a reference to the underlying Object
obj.emplace_back("this is a JSON object", "it's pretty neat");
obj.emplace_back("akey", 3.14);
obj.emplace_back("theanswer", 42);
obj.emplace_back("thequestion", false);
obj.emplace_back("alist", UniValue::Array{{ 1, 2, 3, 4, "hahaha" }});
// the below stringifies or serializes the constructed object
std::cout << UniValue::stringify(uv, 4 /* pretty indent 4 spaces */) << std::endl;
/*
Program output for above is:
{
"this is a JSON object": "it's pretty neat",
"akey": 3.14,
"theanswer": 42,
"thequestion": false,
"alist": [
1,
2,
3,
4,
"hahaha"
]
}
*/
// An example of how to parse an object and examine it
const std::string json{
"{"
" \"this is a JSON object\": \"it's pretty neat\" ,"
" \"akey\": 3.14,"
" \"theanswer\": 42,"
" \"thequestion\": false,"
" \"alist\": [1,2,3,4,\"hahaha\"]"
"}"
};
UniValue uv;
const bool ok = uv.read(json);
assert(ok); // parse of valid json
assert(uv.isObject()); // uv.isObject() is true
const auto &obj = uv.get_obj(); // this would throw std::runtime_error if !uv.isObject()
for (const auto & [key, value] : obj) {
if (key == "theanswer")
std::cout << "the answer is: " << value.get_int64() << std::endl; // throws if the value is not numeric
else if (key == "thequestion")
std::cout << "the question is: " << value.get_bool() << std::endl; // throws if value is not boolean
else if (key == "alist" && value.isArray()) {
std::cout << "the list: " << std::flush;
int i = 0;
for (const auto & item : value.get_array())
std::cout << (i++ ? ", " : "") << item.getValStr(); // getValStr() returns the contents of either a numeric or a string
std::cout << std::endl;
}
}
/*
Program output for above is:
the answer is: 42
the question is: 0
the list: 1, 2, 3, 4, hahaha
*/
- Faster than many implementations. For example, faster than nlohmann for both parsing and for stringification (roughly 2x faster in many cases).
- Install
nlohmann::json
and use thebench
build target to convince yourself of this. - Example bench on my 2019 MacBook Pro:
- Install
Running test on "../bench/semanticscholar-corpus.json" ...
Read 8593351 bytes in 6.723 msec
--- UniValue lib ---
Parsing and re-serializing 10 times ...
Elapsed (msec) - 967.757
Parse (msec) - median: 48.598, avg: 49.845, best: 46.742, worst: 57.972
Serialize (msec) - median: 32.081, avg: 32.789, best: 31.280, worst: 40.098
--- nlohmann::json lib ---
Parsing and re-serializing 10 times ...
Elapsed (msec) - 1860.147
Parse (msec) - median: 91.739, avg: 96.404, best: 89.113, worst: 128.864
Serialize (msec) - median: 45.042, avg: 45.768, best: 44.437, worst: 51.345
- Easier to use, perhaps?
- The entire implementation is wrapped by a single class, called
UniValue
which captures any JSON data item, as well as the whole document, with a single abstraction. Compare this to some of the other fast libraries out there (which shall remain nameless here), some of which are arguably more difficult to use.
- The entire implementation is wrapped by a single class, called
- "Faithful" representation of input JSON.
- Stores the read JSON faithfully without "reinterpreting" anything. For example if the input document had a JSON numeric
1.000000
, this library will re-serialize it verbatim as1.000000
rather than1.0
or1
. - The reason for this: when this library parses JSON numerics, they are internally stored as string fragments (validation is applied, however, to ensure that invalid numerics cannot make it in).
- JSON numerics are actually really parsed to ints or doubles "on-demand" only when the caller actually asks for a number via a getter method.
- Stores the read JSON faithfully without "reinterpreting" anything. For example if the input document had a JSON numeric
- Does not use
std::map
or other map-like structures for JSON objects. JSON objects are implemented as astd::vector
ofstd::pair
s.- This has the benefit of fast inserts when building or parsing the JSON object. (No need to balance r-b trees, etc).
- Inserts preserve the order of insert (which can be an advantage or a disadvantage, depending on what matters to you; they are sort of like Python 3.7+ dicts in that regard).
- Inserts do not check for dupes -- you can have the same key appear twice in the object (something which the JSON specification allows for but discourages).
- Lookups are O(N), though -- but it is felt that for most usages of a C++ app manipulating JSON, this is an acceptable tradeoff.
- In practice many applications merely either parse JSON and iterate over keys, or build the object up once to be sent out on the network or saved to disk immediately -- in such usecases the
std::vector
approach for JSON objects is faster & simpler.
- In practice many applications merely either parse JSON and iterate over keys, or build the object up once to be sent out on the network or saved to disk immediately -- in such usecases the
- Nesting limits:
- Unlimited for serializing/stringification
- 512 for parsing (as a simple DoS measure)
- This can be changed by modifying a compile-time constant in
univalue_read.cpp
.
- This can be changed by modifying a compile-time constant in
UniValue was originally created by Jeff Garzik and is used in node software for many bitcoin-based cryptocurrencies.
BCHN UniValue was a fork of UniValue designed and maintained for use in Bitcoin Cash Node (BCHN).
This library is a fork of the above implementation, optimized and maintained by me, Calin A. Culianu
Unlike the Bitcoin Core fork, this UniValue library contains major improvements to code quality and performance. This library's UniValue API differs slightly from its ancestor projects.
- Optimizations made to parsing (about 1.7x faster than the BCHN library, and several times faster than the Bitcoin Core library)
- Optimizations made to memory consumption (each UniValue nested instance eats only 32 bytes of memory, as opposed to 80 or more bytes in the other implementations)
- Various small nits and improvements to code quality
This library is released under the terms of the MIT license. See COPYING for more information or see https://opensource.org/licenses/MIT.
mkdir build && cd build
cmake -GNinja ..
ninja all check
The above will build and run the unit tests, as well as build the shared library. Alternatively, you can just put the source files from the lib/
and include/
folders into your project.
This library requires C++17 or above.