Skip to content

Commit

Permalink
Added support for processors; resolves #67 (#94)
Browse files Browse the repository at this point in the history
Co-authored-by: Patrick Urbanke <[email protected]>
  • Loading branch information
liuzicheng1987 and liuzicheng1987 authored May 1, 2024
1 parent 32a345d commit 15f2c63
Show file tree
Hide file tree
Showing 157 changed files with 2,522 additions and 556 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ 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:

```cpp
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:

```json
{"firstName":"Homer","lastName":"Simpson","age":45}
```

Or you can use another format, such as YAML.

```cpp
Expand Down
18 changes: 10 additions & 8 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@

1.3) [Struct flattening](https://github.com/getml/reflect-cpp/blob/main/docs/flatten_structs.md) - For making struct A "inherit" the fields of struct B.

1.4) [The rfl::Field-syntax](https://github.com/getml/reflect-cpp/blob/main/docs/field_syntax.md) - Describes an alternative syntax which requires slightly more effort, but allows for some powerful functionalities.
1.4) [Processors](https://github.com/getml/reflect-cpp/blob/main/docs/processors.md) - For modifying the structs before serialization and deserialization. For instance, processors can be used to transform all field names from `snake_case` to `camelCase`.

1.5) [String literals](https://github.com/getml/reflect-cpp/blob/main/docs/literals.md) - For representing strings that can only assume a limited number of enumerated values.
1.5) [The rfl::Field-syntax](https://github.com/getml/reflect-cpp/blob/main/docs/field_syntax.md) - Describes an alternative syntax which requires slightly more effort, but allows for some powerful functionalities.

1.6) [Enums](https://github.com/getml/reflect-cpp/blob/main/docs/enums.md) - Describes how reflect-cpp handles C++ enums.
1.6) [String literals](https://github.com/getml/reflect-cpp/blob/main/docs/literals.md) - For representing strings that can only assume a limited number of enumerated values.

1.7) [std::variant and rfl::TaggedUnion](https://github.com/getml/reflect-cpp/blob/main/docs/variants_and_tagged_unions.md) - For structs that can be one of several formats. This is the equivalent of an OR statement or a sum type in type theory.
1.7) [Enums](https://github.com/getml/reflect-cpp/blob/main/docs/enums.md) - Describes how reflect-cpp handles C++ enums.

1.8) [rfl::Box and rfl::Ref](https://github.com/getml/reflect-cpp/blob/main/docs/rfl_ref.md) - For defining recursive structures.
1.8) [std::variant and rfl::TaggedUnion](https://github.com/getml/reflect-cpp/blob/main/docs/variants_and_tagged_unions.md) - For structs that can be one of several formats. This is the equivalent of an OR statement or a sum type in type theory.

1.9) [rfl::Timestamp](https://github.com/getml/reflect-cpp/blob/main/docs/timestamps.md) - For serializing and deserializing timestamps.
1.9) [rfl::Box and rfl::Ref](https://github.com/getml/reflect-cpp/blob/main/docs/rfl_ref.md) - For defining recursive structures.

1.10) [rfl::Result](https://github.com/getml/reflect-cpp/blob/main/docs/result.md) - For error handling without exceptions.
1.10) [rfl::Timestamp](https://github.com/getml/reflect-cpp/blob/main/docs/timestamps.md) - For serializing and deserializing timestamps.

1.11) [Standard containers](https://github.com/getml/reflect-cpp/blob/main/docs/standard_containers.md) - Describes how reflect-cpp treats containers in the standard library.
1.11) [rfl::Result](https://github.com/getml/reflect-cpp/blob/main/docs/result.md) - For error handling without exceptions.

1.12) [Standard containers](https://github.com/getml/reflect-cpp/blob/main/docs/standard_containers.md) - Describes how reflect-cpp treats containers in the standard library.

## 2) Validation

Expand Down
22 changes: 9 additions & 13 deletions docs/custom_parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ conditions:
You can then implement a custom parser for your class like this:

```cpp
namespace rfl {
namespace parsing {
namespace rfl::parsing {

template <class ReaderType, class WriterType>
struct Parser<ReaderType, WriterType, YourOriginalClass>
: public CustomParser<ReaderType, WriterType, YourOriginalClass,
template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, YourOriginalClass, ProcessorsType>
: public CustomParser<ReaderType, WriterType, ProcessorsType, YourOriginalClass,
YourHelperStruct> {};

} // namespace parsing
} // namespace rfl
} // namespace rfl::parsing
```
## Example
Expand Down Expand Up @@ -77,15 +75,13 @@ struct PersonImpl {
You then implement the custom parser:

```cpp
namespace rfl {
namespace parsing {
namespace rfl::parsing {

template <class ReaderType, class WriterType>
template <class ReaderType, class WriterType, class ProcessorsType>
struct Parser<ReaderType, WriterType, Person>
: public CustomParser<ReaderType, WriterType, Person, PersonImpl> {};
: public CustomParser<ReaderType, WriterType, ProcessorsType, Person, PersonImpl> {};

} // namespace parsing
} // namespace rfl
} // namespace rfl::parsing
```
Now your custom class is fully supported by reflect-cpp. So for instance, you could parse it
Expand Down
108 changes: 108 additions & 0 deletions docs/processors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Processors

Processors can be used to apply transformations to struct serialization and deserialization.

For instance, C++ [usually](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rl-camel) uses `snake_case`, but JSON uses `camelCase`. One way to handle this is `rfl::Rename`, but a more automated way would be to use a *processor*:

```cpp
struct Person {
std::string first_name;
std::string last_name;
std::vector<Person> children;
};

const auto homer =
Person{.first_name = "Homer",
.last_name = "Simpson",
.age = 45};

const auto json_string =
rfl::json::write<rfl::SnakeCaseToCamelCase>(homer);

const auto homer2 =
rfl::json::read<Person, rfl::SnakeCaseToCamelCase>(json_string).value();
```
The resulting JSON string looks like this:
```json
{"firstName":"Homer","lastName":"Simpson","age":45}
```

If you want `PascalCase` instead, you can use the appropriate processor:

```cpp
const auto json_string =
rfl::json::write<rfl::SnakeCaseToPascalCase>(homer);

const auto homer2 =
rfl::json::read<Person, rfl::SnakeCaseToPascalCase>(json_string).value();
```

The resulting JSON string looks like this:

```json
{"FirstName":"Homer","LastName":"Simpson","Age":45}
```

It is also possible to add the struct name as an addtional field, like this:

```cpp
const auto json_string =
rfl::json::write<rfl::AddStructName<"type">>(homer);

const auto homer2 =
rfl::json::read<Person, rfl::AddStructName<"type">>(json_string).value();
```

The resulting JSON string looks like this:

```json
{"type":"Person","first_name":"Homer","last_name":"Simpson","age":45}
```

You can also combine several processors:

It is also possible to add the struct name as an addtional field, like this:

```cpp
const auto json_string =
rfl::json::write<rfl::SnakeCaseToCamelCase, rfl::AddStructName<"type">>(homer);

const auto homer2 =
rfl::json::read<Person, rfl::SnakeCaseToCamelCase, rfl::AddStructName<"type">>(json_string).value();
```

The resulting JSON string looks like this:

```json
{"type":"Person","firstName":"Homer","lastName":"Simpson","age":45}
```

When you have several processors, it is probably more convenient to combine them like this:

```cpp
using Processors = rfl::Processors<
rfl::SnakeCaseToCamelCase, rfl::AddStructName<"type">>;

const auto json_string = rfl::json::write<Processors>(homer);

const auto homer2 = rfl::json::read<Person, Processors>(json_string).value();
```

The resulting JSON string looks like this:

```json
{"type":"Person","firstName":"Homer","lastName":"Simpson","age":45}
```

## Writing your own processors

In principle, writing your own processors is not very difficult. You need to define a struct, which takes has a static method called `process` taking a named tuple as an input and then returning a modified named tuple. The `process` method should accept the type of the original struct as a template parameter.

```cpp
struct MyOwnProcessor {
template <class StructType>
static auto process(auto&& _named_tuple) {...}
};
```
2 changes: 1 addition & 1 deletion docs/structs.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const auto bart = Person{.first_name = "Bart",
.children = std::vector<Person>()};
```

JSON uses Hungarian case, but C++ uses snake case, so you might want to rename your fields:
JSON uses camel case, but C++ uses snake case, so you might want to rename your fields:

```cpp
struct Person {
Expand Down
4 changes: 4 additions & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#pragma warning(disable : 4101)
#endif

#include "rfl/AddStructName.hpp"
#include "rfl/AllOf.hpp"
#include "rfl/AnyOf.hpp"
#include "rfl/Attribute.hpp"
Expand All @@ -19,9 +20,12 @@
#include "rfl/OneOf.hpp"
#include "rfl/Pattern.hpp"
#include "rfl/PatternValidator.hpp"
#include "rfl/Processors.hpp"
#include "rfl/Ref.hpp"
#include "rfl/Rename.hpp"
#include "rfl/Size.hpp"
#include "rfl/SnakeCaseToCamelCase.hpp"
#include "rfl/SnakeCaseToPascalCase.hpp"
#include "rfl/TaggedUnion.hpp"
#include "rfl/Timestamp.hpp"
#include "rfl/Validator.hpp"
Expand Down
32 changes: 32 additions & 0 deletions include/rfl/AddStructName.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef RFL_ADDSTRUCTNAME_HPP_
#define RFL_ADDSTRUCTNAME_HPP_

#include <tuple>

#include "Field.hpp"
#include "Literal.hpp"
#include "internal/StringLiteral.hpp"
#include "internal/get_type_name.hpp"
#include "internal/remove_namespaces.hpp"
#include "make_named_tuple.hpp"

namespace rfl {

template <internal::StringLiteral field_name_>
struct AddStructName {
/// Adds the name of the struct as a new field.
template <class StructType>
static auto process(auto&& _view) {
using LiteralType = Literal<
internal::remove_namespaces<internal::get_type_name<StructType>()>()>;
using FieldType = Field<field_name_, LiteralType>;
const auto add_new_field = [](auto&&... _fields) {
return make_named_tuple(FieldType(LiteralType()), std::move(_fields)...);
};
return std::apply(add_new_field, std::move(_view.fields()));
}
};

} // namespace rfl

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

namespace rfl {

template <class... Ps>
struct Processors;

template <>
struct Processors<> {
template <class T, class NamedTupleType>
static auto process(NamedTupleType&& _named_tuple) {
return _named_tuple;
}
};

template <class Head, class... Tail>
struct Processors<Head, Tail...> {
template <class T, class NamedTupleType>
static auto process(NamedTupleType&& _named_tuple) {
return Processors<Tail...>::template process<T>(
Head::template process<T>(std::move(_named_tuple)));
}
};

} // namespace rfl

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

#include "Field.hpp"
#include "internal/transform_snake_case.hpp"

namespace rfl {

struct SnakeCaseToCamelCase {
public:
/// Replaces all instances of snake_case field names with camelCase.
template <class StructType>
static auto process(auto&& _named_tuple) {
const auto handle_one = []<class FieldType>(FieldType&& _f) {
if constexpr (FieldType::name() != "xml_content") {
return handle_one_field(std::move(_f));
} else {
return std::move(_f);
}
};
return _named_tuple.transform(handle_one);
}

private:
/// Applies the logic to a single field.
template <class FieldType>
static auto handle_one_field(FieldType&& _f) {
using NewFieldType =
Field<internal::transform_snake_case<FieldType::name_,
/*capitalize=*/false>(),
typename FieldType::Type>;
return NewFieldType(_f.value());
}
};

} // namespace rfl

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

#include "Field.hpp"
#include "internal/transform_snake_case.hpp"

namespace rfl {

struct SnakeCaseToPascalCase {
public:
/// Replaces all instances of snake_case field names with PascalCase.
template <class StructType>
static auto process(auto&& _named_tuple) {
const auto handle_one = []<class FieldType>(FieldType&& _f) {
if constexpr (FieldType::name() != "xml_content") {
return handle_one_field(std::move(_f));
} else {
return std::move(_f);
}
};
return _named_tuple.transform(handle_one);
}

private:
/// Applies the logic to a single field.
template <class FieldType>
static auto handle_one_field(FieldType&& _f) {
using NewFieldType =
Field<internal::transform_snake_case<FieldType::name_,
/*capitalize=*/true>(),
typename FieldType::Type>;
return NewFieldType(_f.value());
}
};

} // namespace rfl

#endif
1 change: 1 addition & 0 deletions include/rfl/bson.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef RFL_BSON_HPP_
#define RFL_BSON_HPP_

#include "../rfl.hpp"
#include "bson/Parser.hpp"
#include "bson/Reader.hpp"
#include "bson/Writer.hpp"
Expand Down
4 changes: 2 additions & 2 deletions include/rfl/bson/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
namespace rfl {
namespace bson {

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

}
} // namespace rfl
Expand Down
Loading

0 comments on commit 15f2c63

Please sign in to comment.