The whole idea of this library got started because I wanted to build a schema validation library but it obviously had to work with multiple JSON and Yaml libraries because people use different libraries and some make their own.
How do we make this library work for other folks? we make a wrapper! This is nothing new, there are already some other libraries that do a very similar thing if not the same to accomplish this.
This libraries tries to use concepts and provide easy ways to establish some sort of contract for all these wrappers without relying on any virtual tables so we can remain fast and yet provide a somewhat easy way to make wrappers for arbitrary data containers.
The two main concepts are garlic::ViewLayer and garlic::RefLayer and the idea is that you either want a read-only layer to be able to consume values from it or you want to be able to write to it as well. garlic::RefLayer is a garlic::ViewLayer that has writing methods as well.
These concepts define the bare-minimum set of functions and type alises to make a layer readable. But some layers have additional optional features. You can take adventage of these as well.
Example:
See Encoding Documentation for more details.
As you can see in the above example, you can use a wrapper or a provider for RapidJSON, a populate and fast JSON library to populate a custom class. Because of this concept, as long as you can provide these wrappers for your data containers you can use them with garlic for schema validation and encoding/decoding. You don't have to change your decoding logic for your Config yet you can support reading files from MessagePack, JSON and Yaml and even your own custom containers. In many instances including the example above, compiler might optimize away all these wrappers giving you a near zero cost for this abstraction.
You will see a lot of View
, Reference
and Value
or Document
in garlic.
View is similar to std::string_view in that it is simply a view object. You can wrap various different data container types using a view wrapper like JsonView
for RapidJSON and call your generic methods that know how to read from a garlic::ViewLayer. This is ideal for the following situation:
Reference is similar to passing a value by reference but in this case a small wrapper object carries the reference.
Why is this reference needed? mostly so you don't own an object and also because using iterators would have to return references but obviously it would have to be a wrapper type conforming to garlic::RefLayer
As you can see, it's a bit counter-intuitive that item in the for loop is actually an object you can pass by value cheaply since it's just a reference and is likely to get optimized away. But the important part is that because that is a wrapper object that holds a reference to inner rapidjson::Value instances, it cannot bind to lvalue references in for loops like in the example.
Value is an object that actually manages and holds a data container inside it. You can avoid using rapidjson types altogether and use garlic providers instead.
Document is similar to Value but in some libraries a Document manages the allocations.
There are a very limited set of providers at the moment but the list will grow with time. You can help making them!
There is an existing data container that one can use without relying on any library. See garlic/clove.h
That might be a good choice with libyaml because it occupies less memory than yaml_document_t
and it is faster when getting numbers and boolean values because yaml_document_t only holds strings and everytime you use get_int()
, get_double()
or get_bool()
a conversion from string to the related type has to happen.
NOTE Take a look at the garlic/utility.h for some utility functions you can use with any type conforming to layer concepts.