reflect-cpp is a C++-20 library for fast serialization and deserialization using compile-time reflection, similar to serde in Rust, pydantic in Python, encoding in Go or aeson in Haskell.
As the aformentioned libraries are among the most widely used in the respective languages, reflect-cpp fills an important gap in C++ development. It reduces boilerplate code and increases code safety.
Design principles for reflect-cpp include:
- Close integration with containers from the C++ standard library
- Close adherence to C++ idioms
- Out-of-the-box support for JSON, no dependencies
- Simple installation: If no JSON support is required, reflect-cpp is header-only. For JSON support, only a single source file needs to be compiled.
- Simple extendability to other serialization formats
- Simple extendability to custom classes
- No macros
#include <iostream>
#include <rfl/json.hpp>
#include <rfl.hpp>
// "firstName", "lastName", "birthday" and "children" are the field names
// as they will appear in the JSON. The C++ standard is
// snake case, the JSON standard is camel case, so the names
// will not always be identical.
struct Person {
rfl::Field<"firstName", std::string> first_name;
rfl::Field<"lastName", std::string> last_name;
rfl::Field<"birthday", rfl::Timestamp<"%Y-%m-%d">> birthday;
rfl::Field<"children", std::vector<Person>> children;
};
const auto bart = Person{.first_name = "Bart",
.last_name = "Simpson",
.birthday = "1987-04-19",
.children = std::vector<Person>()};
const auto lisa = Person{
.first_name = "Lisa",
.last_name = "Simpson",
.birthday = "1987-04-19",
.children = rfl::default_value // same as std::vector<Person>()
};
// Returns a deep copy of the original object,
// replacing first_name.
const auto maggie =
rfl::replace(lisa, rfl::make_field<"firstName">(std::string("Maggie")));
const auto homer =
Person{.first_name = "Homer",
.last_name = "Simpson",
.birthday = "1987-04-19",
.children = std::vector<Person>({bart, lisa, maggie})};
// We can now transform this into a JSON string.
const std::string json_string = rfl::json::write(homer);
std::cout << json_string << std::endl;
This results in the following JSON string:
{"firstName":"Homer","lastName":"Simpson","birthday":"1987-04-19","children":[{"firstName":"Bart","lastName":"Simpson","birthday":"1987-04-19","children":[]},{"firstName":"Lisa","lastName":"Simpson","birthday":"1987-04-19","children":[]},{"firstName":"Maggie","lastName":"Simpson","birthday":"1987-04-19","children":[]}]}
We can also create structs from the string:
auto homer2 = rfl::json::read<Person>(json_string).value();
// Fields can be accessed like this:
std::cout << "Hello, my name is " << homer.first_name() << " "
<< homer.last_name() << "." << std::endl;
// Since homer2 is mutable, we can also change the values like this:
homer2.first_name = "Marge";
std::cout << "Hello, my name is " << homer2.first_name() << " "
<< homer2.last_name() << "." << std::endl;
reflect-cpp supports the following containers from the C++ standard library:
std::array
std::deque
std::forward_list
std::map
std::list
std::optional
std::pair
std::set
std::shared_ptr
std::string
std::tuple
std::unique_ptr
std::unordered_map
std::unordered_set
std::variant
std::vector
In addition, it includes the following custom containers:
rfl::Box
: Similar tostd::unique_ptr
, but guaranteed to never be null.rfl::Literal
: An explicitly enumerated string.rfl::NamedTuple
: Similar tostd::tuple
, but with named fields that can be retrieved via their name at compile time.rfl::Ref
: Similar tostd::shared_ptr
, but guaranteed to never be null.rfl::TaggedUnion
: Similar tostd::variant
, but with explicit tags that make parsing more efficient.
Finally, it is very easy to extend full support to your own classes, refer to the documentation for details.
reflect-cpp currently supports the following serialization formats:
- JSON: Out-of-the-box support, no additional dependencies required.
- XML: Requires libxml2 (TODO).
- flexbuffers: Requires flatbuffers (TODO).
reflect-cpp is deliberately designed in a very modular format, using concepts, to make it as easy as possible to support additional serialization formats. Refer to the documentation for details (TODO). PRs related to serialization formats are welcome.
Click here.
At the moment, this is work-in-progress. Here are some things that are still missing:
- Extensive testing
- Benchmarks
- Documentation is unfinished
- Support for std::array, std::unique_ptr and full support for rfl::Box
- Support for rfl::Flatten<std::map<...>> (serde supports this as well)
- Support for std::any (whether we want to support this as all is still being debated).
If you do not need JSON support, then reflect-cpp is header-only. Simply copy the contents of the folder include
into your source repository or add it to your include path.
Simply copy the contents of the folder include
into your source repository or add it to your include path and also add src/yyjson.c
to your source files for compilation.
If you need support for other serialization formats like XML or flexbuffers, you should also include and link the respective libraries, as listed in the previous section.
mkdir build
cd build
cmake ..
make
To compile the tests, do the following:
cd tests
mkdir build
cd build
cmake ..
make
reflect-cpp has been developed by scaleML, a company specializing in software engineering and machine learning for enterprise applications. It is extensively used for getML, a software for automated feature engineering using relational learning.
reflect-cpp is released under the MIT License. Refer to the LICENSE file for details.
reflect-cpp includes YYJSON, the fastest JSON library currently in existence. YYJSON is written by YaoYuan and also released under the MIT License.