Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

**reflect-cpp** is a C++-20 library for **fast serialization, deserialization and validation** using reflection, similar to [pydantic](https://github.com/pydantic/pydantic) in Python, [serde](https://github.com/serde-rs) in Rust, [encoding](https://github.com/golang/go/tree/master/src/encoding) in Go or [aeson](https://github.com/haskell/aeson/tree/master) in Haskell.

Moreover, reflect-cpp is the basis for [sqlgen](https://github.com/getml/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.
Moreover, reflect-cpp is the basis for [sqlgen](https://github.com/getml/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.

Expand Down Expand Up @@ -45,14 +45,14 @@ reflect-cpp and sqlgen fill important gaps in C++ development. They reduce boile
- [Algebraic data types](#algebraic-data-types)
- [Extra fields](#extra-fields)
- [Reflective programming](#reflective-programming)
- [Standard Library Integration](#support-for-containers)
- [Standard Library Integration](#support-for-containers)
- [The team behind reflect-cpp](#the-team-behind-reflect-cpp)
- [License](#license)

### More in our [documentation](https://rfl.getml.com):
- [Installation ↗](https://rfl.getml.com/install/#option-2-compilation-using-cmake)
- [Benchmarks ↗](https://rfl.getml.com/benchmarks)
- [How to contribute ↗](https://rfl.getml.com/contributing)
- [How to contribute ↗](https://rfl.getml.com/contributing)
- [Compiling and running the tests ↗](https://rfl.getml.com/contributing/#compiling-and-running-the-tests)


Expand All @@ -70,6 +70,8 @@ The following table lists the serialization formats currently supported by refle
| BSON | [libbson](https://github.com/mongodb/mongo-c-driver) | >= 1.25.1 | Apache 2.0 | JSON-like binary format |
| Cap'n Proto | [capnproto](https://capnproto.org) | >= 1.0.2 | MIT | Schemaful binary format |
| CBOR | [jsoncons](https://github.com/danielaparker/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](https://uscilab.github.io/cereal/) | >= 1.3.2 | BSD | C++ serialization library with multiple formats |
| CSV | [Apache Arrow](https://arrow.apache.org/) | >= 21.0.0 | Apache 2.0 | Tabular textual format |
| flexbuffers | [flatbuffers](https://github.com/google/flatbuffers) | >= 23.5.26 | Apache 2.0 | Schema-less version of flatbuffers, binary format |
Expand Down Expand Up @@ -119,9 +121,9 @@ The resulting JSON string looks like this:
You can transform the field names from `snake_case` to `camelCase` like this:

```cpp
const std::string json_string =
const std::string json_string =
rfl::json::write<rfl::SnakeCaseToCamelCase>(homer);
auto homer2 =
auto homer2 =
rfl::json::read<Person, rfl::SnakeCaseToCamelCase>(json_string).value();
```

Expand Down Expand Up @@ -150,7 +152,7 @@ last_name: Simpson
age: 45
```

This will work for just about any example in the entire documentation
This will work for just about any example in the entire documentation
and any of the following formats, except where explicitly noted otherwise:

```cpp
Expand Down Expand Up @@ -300,6 +302,32 @@ This will resulting CSV will look like this:
"Homer","Simpson","Springfield",1987-04-19,45,"homer@simpson.com"
```

### Environment variables

reflect-cpp can also read from and write to environment variables using `rfl::env::read` and `rfl::env::write`:

```cpp
#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.

### CLI argument parsing

reflect-cpp can also parse command-line arguments directly into structs using `rfl::cli::read`:
Expand Down Expand Up @@ -404,7 +432,7 @@ struct Item {
Color color;
};

const auto item = Item{.pos_x = 2.0,
const auto item = Item{.pos_x = 2.0,
.pos_y = 3.0,
.shape = Shape::square,
.color = Color::red | Color::blue};
Expand Down Expand Up @@ -616,7 +644,7 @@ In addition, it supports the following custom containers:

- `rfl::Binary`: Used to express numbers in binary format.
- `rfl::Box`: Similar to `std::unique_ptr`, but (almost) guaranteed to never be null.
- `rfl::Bytestring`: An alias for `std::vector<std::byte>`. Supported by Avro, BSON, Cap'n Proto, CBOR, flexbuffers, msgpack and UBJSON.
- `rfl::Bytestring`: An alias for `std::vector<std::byte>`. Supported by Avro, BSON, Cap'n Proto, CBOR, flexbuffers, msgpack and UBJSON.
- `rfl::Commented`: Allows you to add comments to fields (supported by YAML and XML).
- `rfl::Generic`: A catch-all type that can represent (almost) anything.
- `rfl::Hex`: Used to express numbers in hex format.
Expand Down
124 changes: 124 additions & 0 deletions docs/supported_formats/env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Environment Variables

For environment variable support, you must also include the header `<rfl/env.hpp>`.

Environment variables provide a flat, hierarchical key-value store for configuration. This module serializes nested structs into environment variable names using `_` as a separator (e.g., `DATABASE_HOST`) and parses them back.

All field names are converted to uppercase by default.

## Reading and writing

Suppose you have a struct like this:

```cpp
struct Settings {
std::string host;
int port;
double rate;
bool verbose;
};
```

You can serialize to environment variables like this:

```cpp
const auto settings = Settings{...};
rfl::env::write(settings);
```

You can parse from environment variables like this:

```cpp
const rfl::Result<Settings> result = rfl::env::read<Settings>();
```

## Nested structs

Nested structs are serialized using `_` as a separator between field names:

```cpp
struct DatabaseSettings {
std::string host;
int port;
};

struct Settings {
std::string host;
DatabaseSettings database;
};
```

This produces the following environment variables:

- `HOST` — the value of `host`
- `DATABASE_HOST` — the value of `database.host`
- `DATABASE_PORT` — the value of `database.port`

## Arrays

Arrays are serialized with `_INDEX` suffix on the field name:

```cpp
struct Settings {
std::string host;
std::vector<std::string> tags;
};
```

This produces:

- `HOST` — the value of `host`
- `TAGS_0` — the first element
- `TAGS_1` — the second element
- ...

## Enums

Enums are serialized using `rfl::enum_to_string()`, which produces the enumerator name (e.g., `"green"` for `Color::green`). If the enumerator has no name, the underlying integer value is used.

```cpp
enum class Color { red, green, blue };

struct Circle {
float radius;
Color color;
};
```

`color = Color::green` produces `COLOR=green`.

### Flag enums

Flag enums (detected via `enchantum::is_bitflag`) are serialized by joining matching flag names with `|`:

```cpp
enum class Permissions { read = 1, write = 2, execute = 4 };

inline Permissions operator|(Permissions a, Permissions b) {
return static_cast<Permissions>(static_cast<int>(a) | static_cast<int>(b));
}
```

`permissions = Permissions::read | Permissions::write` produces `PERMISSIONS=read|write`.

## Tagged unions

Tagged unions work as expected, with the discriminant field serialized as a regular field:

```cpp
using Shapes = rfl::TaggedUnion<"shape", Circle, Square, Rectangle>;
```

This produces a `SHAPE` field with the variant name (e.g., `SHAPE=RECTANGLE`).

## Processors

You can pass processors to `read` and `write` to customize behavior. By default, `ToAllCaps` is applied, which converts all field names to uppercase. You can add additional processors to the template parameter list:

```cpp
rfl::env::write<SomeProcessor>(settings);
```

## Custom constructors

Custom constructors are not supported for environment variable parsing.
1 change: 1 addition & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "rfl/SnakeCaseToPascalCase.hpp"
#include "rfl/TaggedUnion.hpp"
#include "rfl/Timestamp.hpp"
#include "rfl/ToAllCaps.hpp"
#include "rfl/UnderlyingEnums.hpp"
#include "rfl/Validator.hpp"
#include "rfl/Variant.hpp"
Expand Down
44 changes: 44 additions & 0 deletions include/rfl/ToAllCaps.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#ifndef RFL_TOALLCAPS_HPP_
#define RFL_TOALLCAPS_HPP_

#include "Field.hpp"
#include "internal/is_rename.hpp"
#include "internal/transform_case.hpp"

namespace rfl {

struct ToAllCaps {
public:
/// Replaces all instances of field names with all uppercase letters.
/// A named tuple is a tuple-like structure where each element has a name/key.
/// @tparam StructType The type of the struct being processed
/// @param _named_tuple The named tuple containing the struct's fields
/// @return A new named tuple with field names converted to all uppercase
template <class StructType>
static auto process(const auto& _named_tuple) {
return _named_tuple.transform([]<class FieldType>(const FieldType& _f) {
if constexpr (FieldType::name() != "xml_content" &&
!internal::is_rename_v<typename FieldType::Type>) {
return handle_one_field(_f);
} else {
return _f;
}
});
}

private:
/// Applies the all-uppercase transformation to a single field.
/// @tparam FieldType The type of the field being transformed
/// @param _f The field to transform
/// @return A new field with the transformed name and the same value
template <class FieldType>
static auto handle_one_field(const FieldType& _f) {
using NewFieldType = Field<internal::to_all_caps<FieldType::name_>(),
typename FieldType::Type>;
return NewFieldType(_f.value());
}
};

} // namespace rfl

#endif
11 changes: 11 additions & 0 deletions include/rfl/env.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef RFL_ENV_HPP_
#define RFL_ENV_HPP_

#include "../rfl.hpp"
#include "env/Parser.hpp"
#include "env/Reader.hpp"
#include "env/Writer.hpp"
#include "env/read.hpp"
#include "env/write.hpp"

#endif
31 changes: 31 additions & 0 deletions include/rfl/env/Parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef RFL_ENV_PARSER_HPP_
#define RFL_ENV_PARSER_HPP_

#include "../parsing/AreReaderAndWriter.hpp"
#include "../parsing/Parser_base.hpp"
#include "Reader.hpp"
#include "Writer.hpp"

namespace rfl::parsing {

template <class ProcessorsType, class... FieldTypes>
requires AreReaderAndWriter<env::Reader, env::Writer,
NamedTuple<FieldTypes...>>

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15 (tests)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15 (tests)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-18)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15-intel (tests)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++23-llvm-18)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-17)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-16)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-16)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-18)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++23-llvm-18)

use of undeclared identifier 'NamedTuple'

Check failure on line 13 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-17)

use of undeclared identifier 'NamedTuple'
struct Parser<env::Reader, env::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<
env::Reader, env::Writer,
/*_ignore_empty_containers=*/true,
/*_all_required=*/internal::no_optionals_v<ProcessorsType>,
/*_no_field_names_=*/false, ProcessorsType, FieldTypes...> {};

} // namespace rfl::parsing

namespace rfl::env {

template <class T, class ProcessorsType>
using Parser = parsing::Parser<Reader, Writer, T, ProcessorsType>;

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15 (tests)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15 (tests)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-18)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / macos-15-intel (tests)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++23-llvm-18)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-17)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-16)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-16)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-18)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++23-llvm-18)

template argument for template type parameter must be a type

Check failure on line 27 in include/rfl/env/Parser.hpp

View workflow job for this annotation

GitHub Actions / (headers-C++20-llvm-17)

template argument for template type parameter must be a type

} // namespace rfl::env

#endif
Loading
Loading