Skip to content

Commit

Permalink
Added rfl::NoOptionals; resolved #100 (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
liuzicheng1987 authored May 26, 2024
1 parent 43ebb0b commit 1eed713
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 17 deletions.
3 changes: 2 additions & 1 deletion docs/optional_fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ But Bart doesn't have any children. After all, he's only 10 years old. To indica
we can use either `std::optional`, `std::shared_ptr` or `std::unique_ptr`.

If `std::optional` is `std::nullopt` or any of the smart pointers is a `nullptr`,
then the field will not be written to the JSON string. Likewise, when you read the JSON string, the fields will not be required.
then the field will not be written to the JSON string. Likewise, when you read the JSON string, the fields will not be required. If you
want the fields to be required, you can use the `rfl::NoOptionals` processor, please refer to the section on processors.

So this is how we could rewrite the previous example:

Expand Down
72 changes: 62 additions & 10 deletions docs/processors.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,91 @@ The resulting JSON string looks like this:
{"firstName":"Homer","lastName":"Simpson","age":45}
```

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

reflect-cpp currently supports the following processors:

- `rfl::AddStructName`
- `rfl::NoOptionals`
- `rfl::SnakeCaseToCamelCase`
- `rfl::SnakeCaseToPascalCase`

### `rfl::AddStructName`

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

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

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

The resulting JSON string looks like this:

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

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

As we have seen in the section on optional fields, when a `std::optional` is
`std::nullopt`, it is usually not written at all. But if you want them to be explicitly
written as `null`, you can use this processor. The same thing applies to `std::shared_ptr` and
`std::unique_ptr`.

```cpp
struct Person {
std::string first_name;
std::string last_name = "Simpson";
std::optional<std::string> town = std::nullopt;
};

const auto homer = Person{.first_name = "Homer"};

rfl::json::write<rfl::NoOptionals>(homer);
```
The resulting JSON string looks like this:
```json
{"first_name":"Homer","last_name":"Simpson","town":null}
```

By default, `rfl::json::read` will accept both `"town":null` and just
leaving out the field `town`. However, if you want to require the field
`town` to be included, you can add `rfl::NoOptionals` to `read`:

```
rfl::json::read<Person, rfl::NoOptionals>(json_string);
```

### `rfl::SnakeCaseToCamelCase`

Please refer to the example above.

### `rfl::SnakeCaseToPascalCase`

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

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

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

The resulting JSON string looks like this:

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

You can also combine several processors:
## Combining several processors

You can combine several processors:

```cpp
const auto json_string =
Expand Down Expand Up @@ -103,4 +155,4 @@ struct MyOwnProcessor {
template <class StructType>
static auto process(auto&& _named_tuple) {...}
};
```
```
1 change: 1 addition & 0 deletions include/rfl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "rfl/Flatten.hpp"
#include "rfl/Literal.hpp"
#include "rfl/NamedTuple.hpp"
#include "rfl/NoOptionals.hpp"
#include "rfl/OneOf.hpp"
#include "rfl/Pattern.hpp"
#include "rfl/PatternValidator.hpp"
Expand Down
18 changes: 18 additions & 0 deletions include/rfl/NoOptionals.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef RFL_NOOPTIONALS_HPP_
#define RFL_NOOPTIONALS_HPP_

namespace rfl {

/// This is a "fake" processor - it doesn't do much in itself, but its
/// inclusion instructs the parsers to require the inclusion of all fields.
struct NoOptionals {
public:
template <class StructType>
static auto process(auto&& _named_tuple) {
return _named_tuple;
}
};

} // namespace rfl

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

#include <type_traits>

#include "internal/is_no_optionals_v.hpp"

namespace rfl {

template <class... Ps>
struct Processors;

template <>
struct Processors<> {
static constexpr bool all_required_ = false;

template <class T, class NamedTupleType>
static auto process(NamedTupleType&& _named_tuple) {
return _named_tuple;
Expand All @@ -16,6 +22,10 @@ struct Processors<> {

template <class Head, class... Tail>
struct Processors<Head, Tail...> {
static constexpr bool all_required_ =
std::disjunction_v<internal::is_no_optionals<Head>,
internal::is_no_optionals<Tail>...>;

template <class T, class NamedTupleType>
static auto process(NamedTupleType&& _named_tuple) {
return Processors<Tail...>::template process<T>(
Expand Down
29 changes: 29 additions & 0 deletions include/rfl/internal/is_no_optionals_v.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#ifndef RFL_INTERNAL_ISNOOPTIONALS_HPP_
#define RFL_INTERNAL_ISNOOPTIONALS_HPP_

#include <tuple>
#include <type_traits>
#include <utility>

#include "../NoOptionals.hpp"

namespace rfl {
namespace internal {

template <class T>
class is_no_optionals;

template <class T>
class is_no_optionals : public std::false_type {};

template <>
class is_no_optionals<NoOptionals> : public std::true_type {};

template <class T>
constexpr bool is_no_optionals_v =
is_no_optionals<std::remove_cvref_t<std::remove_pointer_t<T>>>::value;

} // namespace internal
} // namespace rfl

#endif
4 changes: 2 additions & 2 deletions include/rfl/parsing/Parser_named_tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ template <class R, class W, class... FieldTypes, class ProcessorsType>
requires AreReaderAndWriter<R, W, NamedTuple<FieldTypes...>>
struct Parser<R, W, NamedTuple<FieldTypes...>, ProcessorsType>
: public NamedTupleParser<R, W, /*_ignore_empty_containers=*/false,
/*_all_required=*/false, ProcessorsType,
FieldTypes...> {
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, FieldTypes...> {
};

} // namespace parsing
Expand Down
3 changes: 2 additions & 1 deletion include/rfl/parsing/Parser_tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ template <class R, class W, class... Ts, class ProcessorsType>
requires AreReaderAndWriter<R, W, std::tuple<Ts...>>
struct Parser<R, W, std::tuple<Ts...>, ProcessorsType>
: public TupleParser<R, W, /*_ignore_empty_containers=*/false,
/*_all_required=*/false, ProcessorsType, Ts...> {
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, Ts...> {
};

} // namespace parsing
Expand Down
4 changes: 2 additions & 2 deletions include/rfl/xml/Parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ struct Parser<xml::Reader, xml::Writer, NamedTuple<FieldTypes...>,
ProcessorsType>
: public NamedTupleParser<xml::Reader, xml::Writer,
/*_ignore_empty_containers=*/true,
/*_all_required=*/false, ProcessorsType,
FieldTypes...> {
/*_all_required=*/ProcessorsType::all_required_,
ProcessorsType, FieldTypes...> {
};

} // namespace parsing
Expand Down
33 changes: 33 additions & 0 deletions tests/json/test_no_optionals.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <iostream>
#include <rfl.hpp>
#include <rfl/json.hpp>
#include <source_location>
#include <string>
#include <vector>

#include "write_and_read.hpp"

namespace test_no_optionals {

struct Person {
rfl::Rename<"firstName", std::string> first_name;
rfl::Rename<"lastName", std::string> last_name = "Simpson";
rfl::Rename<"children", std::optional<std::vector<Person>>> children;
};

TEST(json, test_no_optionals) {
const auto bart = Person{.first_name = "Bart"};

const auto lisa = Person{.first_name = "Lisa"};

const auto maggie = Person{.first_name = "Maggie"};

const auto homer =
Person{.first_name = "Homer",
.children = std::vector<Person>({bart, lisa, maggie})};

write_and_read<rfl::NoOptionals>(
homer,
R"({"firstName":"Homer","lastName":"Simpson","children":[{"firstName":"Bart","lastName":"Simpson","children":null},{"firstName":"Lisa","lastName":"Simpson","children":null},{"firstName":"Maggie","lastName":"Simpson","children":null}]})");
}
} // namespace test_no_optionals
2 changes: 1 addition & 1 deletion tests/json/test_optional_fields.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct Person {
rfl::Rename<"children", std::optional<std::vector<Person>>> children;
};

TEST(json, insert_name_here) {
TEST(json, test_optional_fields) {
const auto bart = Person{.first_name = "Bart"};

const auto lisa = Person{.first_name = "Lisa"};
Expand Down

0 comments on commit 1eed713

Please sign in to comment.