Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat formats: automatic serialization/deserialization based on Boost.PFR #469

Conversation

linuxnyasha
Copy link
Contributor

@linuxnyasha linuxnyasha commented Dec 18, 2023

Example

#include <userver/formats/json.hpp>
#include <iostream>


struct SomeStruct {
  std::optional<int> field;
  std::string field2;
};

template <>
inline constexpr auto userver::formats::universal::kSerialization<SomeStruct> =
  userver::formats::universal::SerializationConfig<SomeStruct>::Create()
  .With<"field">(Default<124>, Min<12>, Max<1221>)
  .With<"field2">(Pattern<"^[0-9]+$">);

int main() {
  userver::formats::json::ValueBuilder builder(SomeStruct{{}, "121"});
  auto json = builder.ExtractValue();
  std::cout << userver::formats::json::ToString(json) << std::endl; // {"field":124,"field2":"121"}
  std::cout << json.As<SomeStruct>().field << std::endl; // 124
};

@Anton3 Anton3 changed the title Automatic serialization/deserialization on boost pfr static reflection feat formats: automatic serialization/deserialization based on Boost.PFR Dec 18, 2023
universal/include/userver/formats/json.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/parse/common.hpp Outdated Show resolved Hide resolved
universal/include/userver/utils/type_list.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/json/universal.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/json/universal.hpp Outdated Show resolved Hide resolved
return this->contents[index];
};

constexpr operator char&() const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wut

};
constexpr ConstexprString(std::array<char, Size> data) noexcept : contents(data) {};
template <std::size_t OtherSize>
constexpr ConstexprString<Size + OtherSize> operator+(const ConstexprString<OtherSize>& other) const noexcept {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like operator+ is not really needed for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be used, for example, to generate constexpr SQL queries via Boost.PFR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll give it a second thought later

universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
universal/include/userver/utils/constexpr_string.hpp Outdated Show resolved Hide resolved
template <std::size_t Size = 1>
struct ConstexprString {
static constexpr auto kSize = Size;
std::array<char, Size> contents;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<array> brings in <algorithm> in typical stdlib implementations, and that's a very heavy header. We try to minimize the usage of <array> in public headers

universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
@@ -14,7 +14,7 @@ namespace formats::parse {
namespace impl {

template <typename T, typename Value>
inline bool Is(Value&& value) {
constexpr inline bool Is(Value&& value) {
if constexpr(std::is_convertible_v<T, std::int64_t>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

double and bool are convertible to int64_t

universal/include/userver/formats/parse/try_parse.hpp Outdated Show resolved Hide resolved
@linuxnyasha linuxnyasha force-pushed the feature_reflection_serialize branch from 0784a5f to a81e72c Compare December 19, 2023 11:26
} // namespace impl

template <typename T, typename... Params>
class SerializationConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[big] After some internal discussion: we want to reduce the amount of template metaprogramming and make SerializationConfig value-based. Details of struct fields serialization and so on will be stored inside SerializationConfig itself, not inside its template parameters.

SerializationConfig should contain a tuple of FieldConfig for each field. FieldConfig should be parametrized with the parsed type. The config creation will look like this:

.With<"field">({.max_items = 10, .items = {.min = 42}})

With takes a single parameter of type FieldConfig<(type of "field")>, which is a struct with all optional members, like std::optional<std::size_t> max_items and FieldConfig<(type of items)> items. This config is assigned to the SerializationConfig's tuple element corresponding to "field".

FieldConfig should be able to be specialized for the parsed type. For example, FieldConfig will be specialized for array-like ranges to include min_items, max_items and items fields, and for numeric types to include min and max fields of appropriate type.

FieldConfig for structs should contain a single reference to the struct's SerializationConfig, which should be default-initialized with formats::universal::kSerialization<T>. The "reference" part is important, as it allows to create recursive structs.

Add a test that contains a simple recursive type:

struct Node {
  int value;
  std::vector<Node> children;
};

Test universal serialization on it.

That's the gist of the vision for value-based serialization config. Feel free to ask questions here or in PM.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions will still be able to use if constexpr and alike by passing around const FieldConfig<T>& in template parameters. Remember that all those SerializationConfig and FieldConfig are stored inside variable templates, so it's possible to use addresses of those variables and their contents in templates, even in C++17

@Anton3
Copy link
Member

Anton3 commented Dec 28, 2023

Continuing in #472

@Anton3 Anton3 closed this Dec 28, 2023
@github-actions github-actions bot locked and limited conversation to collaborators Dec 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants