A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, Avro, Boost Serialization, BSON, Cap'n Proto, CBOR, Cere
A C++20 library for fast serialization, deserialization and validation using reflection. Supports JSON, Avro, Boost Serialization, BSON, Cap'n Proto, CBOR, Cere
** Documentation**: https://rfl.getml.com
reflect-cpp is a C++-20/C++-26 library for fast serialization, deserialization and validation using reflection, similar to pydantic in Python, serde in Rust, encoding in Go or aeson in Haskell.
reflect-cpp supports C++-26 reflection, but most of the functionality is also available in C++-20, except where explicitly noted otherwise.
Moreover, reflect-cpp is the basis for sqlgen, a modern, type-safe ORM and SQL query generator for C++20, inspired by Python's SQLAlchemy/SQLModel and Rust's Diesel. It provides a fluent, composable interface for database operations with compile-time type checking and SQL injection protection.
reflect-cpp and sqlgen fill important gaps in C++ development. They reduce boilerplate code and increase code safety. Together, they enable reliable and efficient ETL pipelines.
reflect-cpp provides a unified reflection-based interface across different serialization formats. It is deliberately designed in a very modular way, using concepts, to make it as easy as possible to interface various C or C++ libraries related to serialization. Refer to the documentation for details.
The following table lists the serialization formats currently supported by reflect-cpp and the underlying libraries used:
| Format | Library | Version | License | Remarks |
|---|---|---|---|---|
| JSON | yyjson | >= 0.8.0 | MIT | out-of-the-box support, included in this repository |
| Avro | avro-c | >= 1.11.3 | Apache 2.0 | Schemaful binary format |
| Boost.Serialization | Boost.Serialization | >= 1.74.0 | BSL 1.0 | Streaming binary format with archive interop |
| BSON | libbson | >= 1.25.1 | Apache 2.0 | JSON-like binary format |
| Cap'n Proto | capnproto | >= 1.0.2 | MIT | Schemaful binary format |
| CBOR | jsoncons | >= 0.176.0 | BSL 1.0 | JSON-like binary format |
| cli | (none) | (none) | MIT | Command line interface |
| env | (none) | (none) | MIT | Environment variables |
| Cereal | Cereal | >= 1.3.2 | BSD | C++ serialization library with multiple formats |
| CSV | Apache Arrow | >= 21.0.0 | Apache 2.0 | Tabular textual format |
| flexbuffers | flatbuffers | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format |
| msgpack | msgpack-c | >= 6.0.0 | BSL 1.0 | JSON-like binary format |
| parquet | Apache Arrow | >= 21.0.0 | Apache 2.0 | Tabular binary format |
| TOML | toml++ | >= 3.4.0 | MIT | Textual format with an emphasis on readability |
| UBJSON | jsoncons | >= 0.176.0 | BSL 1.0 | JSON-like binary format |
| XML | pugixml | >= 1.14 | MIT | Textual format used in many legacy projects |
| YAML | yaml-cpp | >= 0.8.0 | MIT | Textual format with an emphasis on readability |
| yas | yas | >= 7.1.0 | BSL 1.0 | Very fast and compact serialization library |
Support for more serialization formats is in development. Refer to the issues for details.
Please also refer to the conanfile.py or vcpkg.json in this repository.
#include <rfl/json.hpp>
#include <rfl.hpp>
struct Person {
std::string first_name;
std::string last_name;
int age;
};
const auto homer =
Person{.first_name = "Homer",
.last_name = "Simpson",
.age = 45};
// We can now write into and read from a JSON string.
const std::string json_string = rfl::json::write(homer);
auto homer2 = rfl::json::read<Person>(json_string).value();The resulting JSON string looks like this:
{"first_name":"Homer","last_name":"Simpson","age":45}You can transform the field names from snake_case to camelCase like this:
const std::string json_string =
rfl::json::write<rfl::SnakeCaseToCamelCase>(homer);
auto homer2 =
rfl::json::read<Person, rfl::SnakeCaseToCamelCase>(json_string).value();The resulting JSON string looks like this:
{"firstName":"Homer","lastName":"Simpson","age":45}Or you can use another format, such as YAML.
#include <rfl/yaml.hpp>
// ... (same as above)
const std::string yaml_string = rfl::yaml::write(homer);
auto homer2 = rfl::yaml::read<Person>(yaml_string).value();The resulting YAML string looks like this:
first_name: Homer
last_name: Simpson
age: 45This will work for just about any example in the entire documentation and any of the following formats, except where explicitly noted otherwise:
……This results in the following JSON string:
{"firstName":"Homer","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":45,"email":"[email protected]","children":[{"firstName":"Bart","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":10,"email":"[email protected]","children":[]},{"firstName":"Lisa","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":8,"email":"[email protected]","children":[]},{"firstName":"Maggie","lastName":"Simpson","town":"Springfield","birthday":"1987-04-19","age":0,"email":"[email protected]","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 also supports tabular data formats, like CSV or Parquet:
…This will resulting CSV will look like this:
"first_name","last_name","town","birthday","age","email"
"Bart","Simpson","Springfield",1987-04-19,10,"[email protected]"
"Lisa","Simpson","Springfield",1987-04-19,8,"[email protected]"
"Maggie","Simpson","Springfield",1987-04-19,0,"[email protected]"
"Homer","Simpson","Springfield",1987-04-19,45,"[email protected]"reflect-cpp can also read from and write to environment variables using rfl::env::read and rfl::env::write:
#include <rfl/env.hpp>
struct Config {
std::string host;
int port;
bool verbose;
};
const auto config = Config{.host = "localhost", .port = 8080, .verbose = true};
rfl::env::write(config);
// Sets HOST=localhost, PORT=8080, VERBOSE=true
const auto config2 = rfl::env::read<Config>().value();Nested structs are flattened with _ as a separator (e.g., DATABASE_HOST).
Arrays use _INDEX suffix (e.g., TAGS_0, TAGS_1).
Enums are serialized via their enumerator name (e.g., "green" for Color::green).
All field names are converted to uppercase by default.
reflect-cpp can also parse command-line arguments directly into structs using rfl::cli::read:
#include <rfl/cli.hpp>
struct Config {
std::string host_name;
int port;
bool verbose;
std::vector<std::string> tags;
};
int main(int argc, char* argv[]) {
const auto config = rfl::cli::read<Config>(argc, argv).value();
// ./app --host-name=localhost --port=8080 --verbose --tags=a,b,c
}Field names are automatically converted from snake_case to kebab-case (host_name matches --host-name).
You can mark fields as positional arguments with rfl::Positional<T> and add single-character aliases with rfl::Short<"x", T>:
struct Config {
rfl::Positional<std::string> input_file;
rfl::Short<"o", std::string> output_dir;
rfl::Short<"v", bool> verbose;
int count;
};
// ./app data.csv -o /tmp/out -v --count=10Nested structs, std::optional, std::vector, enums, rfl::Flatten and rfl::Rename are all supported. Refer to the documentation for details.
reflect-cpp returns clear and comprehensive error messages:
const std::string faulty_json_string =
R"({"firstName":"Homer","lastName":12345,"town":"Springfield","birthday":"04/19/1987","age":145,"email":"hNo open issues yet, or sync has not completed.