JSON Voorhees is a JSON library written for the C++ programmer who wants to be productive in this modern world. What does that mean? There are a ton of JSON libraries floating around touting how they are "modern" C++ and so on. But who really cares? JSON Voorhees puts the focus more on the resulting C++ than any "modern" feature set. This means the library does not skip on string encoding details like having full support for UTF-8. Are there "modern" features? Sure, but this library is not meant to be a gallery of them – a good API should get out of your way and let you work. It is hosted onGitHub and sports an Apache License, so use it anywhere you need.
Features include (but are not necessarily limited to):
value should not feel terribly different from a C++ Standard Library containeroperator<<parsevalue is 16 bytes on a 64-bit platform)value into a C++ type usingextract<T>to_jsonJSON Voorhees is designed with ease-of-use in mind. So let's look at some code!
The central class of JSON Voorhees is thejsonv::value. This class represents a JSON AST and is somewhat of a dynamic type. This can make things a little bit awkward for C++ programmers who are used to static typing. Don't worry about it – you can learn to love it.
Putting values of different types is super-easy.
Output:
If that isn't convenient enough for you, there is a user-defined literal_json in thejsonv namespace you can use
JSON is dynamic, which makes value access a bit more of a hassle, but JSON Voorhees aims to make it not too horrifying for you. Ajsonv::value has a number of accessor methods named things likeas_integer andas_string which let you access the value as if it was that type. But what if it isn't that type? In that case, the function will throw ajsonv::kind_error with a bit more information as to what rule you violated.
Output:
You can also deal with container types in a similar manner that you would deal with the equivalent STL container type, with some minor caveats. Because thevalue_type of a JSON object and JSON array are different, they have different iterator types in JSON Voorhees. They are aptly-namedobject_iterator andarray_iterator. The access methods for these iterators arebegin_object /end_object andbegin_array /end_array, respectively. The object interface behaves exactly like you would expect astd::map<std::string,jsonv::value> to, while the array interface behaves just like astd::deque<jsonv::value> would.
Output:
The iterator typeswork. This means you are free to use all of the C++ things just like you would a regular container. To use a ranged-based for, simply callas_array oras_object. Everything from <algorithm> and <iterator> or any other library works great with JSON Voorhees. Bring those templates on!
Output:
Usually, the reason people are using JSON is as a data exchange format, either for communicating with other services or storing things in a file or a database. To do this, you need toencode yourjson::value into anstd::string andparse it back. JSON Voorhees makes this very easy for you.
Output:
If you are paying close attention, you might have noticed that the value for the"infinity" looks a little bit morenull thaninfinity. This is because, much like mathematicians before Anaximander, JSON has no concept of infinity, so it is actuallyillegal to serialize a token likeinfinity anywhere. By default, when an encoder encounters an unrepresentable value in the JSON it is trying to encode, it outputsnull instead. If you wish to change this behavior, implement your ownjsonv::encoder (or derive fromjsonv::ostream_encoder). If you ran the example program, you might have noticed that the return code was 1, meaning the value you put into the file and what you got from it were not equal. This is because all the type and value information is still kept around in the in-memoryobj. It is only upon encoding that information is lost.
Getting tired of all this compact rendering of your JSON strings? Want a little more whitespace in your life? Thenjsonv::ostream_pretty_encoder is the class for you! Unlike our standardcompact encoder, this guy will put newlines and indentation in your JSON so you can present it in a way more readable format.
Compile that code and you now have your own little JSON prettification program!
Most of the time, you do not want to deal withjsonv::value instances directly. Instead, most people prefer to convertjsonv::value instances into their own strong C++class orstruct. JSON Voorhees provides utilities to make this easy for you to use. At the end of the day, you should be able to create an arbitrary C++ type withjsonv::extract<my_type>(value) and create ajsonv::value from your arbitrary C++ type withjsonv::to_json(my_instance).
Let's start with converting ajsonv::value into a custom C++ type withjsonv::extract<T>.
Output:
Overall, this is not very complicated. We did not do anything that could not have been done through a little use ofas_integer andas_string. So what is thisextract giving us?
The real power comes in when we start talking aboutjsonv::formats. These objects provide a set of rules to encode and decode arbitrary types. So let's make a C++class for our JSON object and write a special constructor for it.
Output:
There is a lot going on in that example, so let's take it one step at a time. First, we are creating amy_type object to store our values, which is nice. Then, we gave it a funny-looking constructor:
This is anextracting constructor. All that means is that it has those two arguments: ajsonv::value and ajsonv::extraction_context. Thejsonv::extraction_context is an optional, but extremely helpful class. Inside the constructor, we use thejsonv::extraction_context to access the values of the incoming JSON object in order to build our object.
Ajsonv::extractor is a type that knows how to take ajsonv::value and create some C++ type out of it. In this case, we are creating ajsonv::extractor_construction, which is a subtype that knows how to call the constructor of a type. There are all sorts ofjsonv::extractor implementations injsonv/serialization.hpp, so you should be able to find one that fits your needs.
Now things are starting to get interesting. Thejsonv::formats object is a collection ofjsonv::extractors, so we create one of our own and add thejsonv::extractor* from the static function ofmy_type. Now,local_formatsonly knows how to extract instances ofmy_type – it doesnot know even the most basic things like how to extract anint. We usejsonv::formats::compose to create a new instance ofjsonv::formats that combines the qualities oflocal_formats (which knows how to deal withmy_type) and thejsonv::formats::defaults (which knows how to deal with things likeint andstd::string). Theformats instance now has the power to do everything we need!
This is not terribly different from the example before, but now we are explicitly passing ajsonv::formats object to the function. If we had not providedformat as an argument here, the function would have thrown ajsonv::extraction_error complaining about how it did not know how to extract amy_type.
JSON Voorhees also allows you to convert from your C++ structures into JSON values, usingjsonv::to_json. It should feel like a mirrorjsonv::extract, with similar argument types and many shared concepts. Just like extraction,jsonv::to_json uses thejsonv::formats class, but it uses ajsonv::serializer to convert from C++ into JSON.
Output:
Does all this seem a little bitmanual to you? Creating anextractor andserializer for every single type can get a little bit tedious. Unfortunately, until C++ has a standard way to do reflection, we must specify the conversions manually. However, thereis an easier way! That way is theSerialization Builder DSL.
Let's start with a couple of simple structures:
Let's make aformats for them using the DSL:
What is going on there? The giant chain of function calls is building up a collection of type adapters into aformats for you. The indentation shows the intent – the.member("a", &foo::a) is attached to the typeadapter forfoo (if you tried to specify&bar::y in that same place, it would fail to compile). Each function call returns a reference back to the builder so you can chain as many of these together as you want to. Thejsonv::formats_builder is a proper object, so if you wish to spread out building your type adapters into multiple functions, you can do that by passing around an instance.
The two most-used functions aretype andmember.type defines ajsonv::adapter for the C++ class provided at the template parameter. All of the calls before the secondtype call modify the adapter forfoo. There, we attach members with themember function. This tells theformats how to encode and extract each of the specified members to and from a JSON object using the provided string as the key. The extra function calls likedefault_value,since anduntil are just a could of the many functions available to modify how the members of the type get transformed.
Theformats we built would be perfectly capable of serializing to and extracting from this JSON document:
For a more in-depth reference, see theSerialization Builder DSL page.
JSON Voorhees takes a "batteries included" approach. A few building blocks for powerful operations can be found in thealgorithm.hpp header file.
One of the simplest operations you can perform is themap operation. This operation takes in somejsonv::value and returns another. Let's try it.
If everything went right, you should see a number:
Okay, so that was not very interesting. To be fair, that is not the most interesting example of usingmap, but it is enough to get the general idea of what is going on. This operation is so common that it is a member function ofvalue asjsonv::value::map. Let's make things a bit more interesting andmap anarray...
Now we're starting to get somewhere!
Themap function maps over whatever the contents of thejsonv::value happens to be and returns something for you based on thekind. This simple concept is so ubiquitous thatEugenio Moggi named it amonad. If you're feeling adventurous, try usingmap with anobject or chaining multiplemap operations together.
Another common building block is the functionjsonv::traverse. This function walks a JSON structure and calls a some user-provided function.
Now we have a tiny little program! Here's what happens when I pipe{ "bar": [1, 2, 3], "foo": "hello" } into the program:
Imagine the possibilities!
All of thereally powerful functions can be found inutil.hpp. My personal favorite isjsonv::merge. The idea is simple: it merges two (or more) JSON values into one.
Output:
You might have noticed the use ofstd::move into themerge function. Like most functions in JSON Voorhees,merge takes advantage of move semantics. In this case, the implementation will move the contents of the values instead of copying them around. While it may not matter in this simple case, if you have large JSON structures, the support for movement will save you a ton of memory.