Skip to content

Commit 280f6ae

Browse files
committed
Make serializer generic
1 parent 8e6c686 commit 280f6ae

6 files changed

Lines changed: 268 additions & 104 deletions

File tree

include/iris/marshal/serialize.hpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#ifndef IRIS_ZZ_MARSHAL_SERIALIZE_HPP
2+
#define IRIS_ZZ_MARSHAL_SERIALIZE_HPP
3+
4+
// SPDX-License-Identifier: MIT
5+
6+
#include <iris/config.hpp> // IWYU pragma: keep
7+
8+
#include <iris/marshal/serialize_traits.hpp>
9+
#include <iris/marshal/detail/field.hpp>
10+
11+
#include <iris/alloy/utility.hpp>
12+
13+
#include <string_view>
14+
15+
namespace iris::marshal {
16+
17+
namespace detail {
18+
19+
enum dummy_unscoped_enum {};
20+
enum struct dummy_scoped_enum {};
21+
22+
} // detail
23+
24+
template<class W>
25+
concept writer =
26+
requires {
27+
typename W::format;
28+
} &&
29+
requires(W& w, std::string_view map_key) {
30+
w.null();
31+
w.begin_array();
32+
w.end_array();
33+
w.begin_object();
34+
w.map_key(map_key);
35+
w.end_object();
36+
};
37+
38+
template<writer W>
39+
struct basic_save_fn
40+
{
41+
using format = W::format;
42+
43+
template<serializable<format> T>
44+
requires requires {
45+
typename W::result_type;
46+
} && std::constructible_from<W, typename W::result_type&>
47+
static constexpr void operator()(W::result_type& res, T const& value)
48+
{
49+
W w{res};
50+
basic_save_fn{}(w, value);
51+
}
52+
53+
template<serializable_scalar<format> T>
54+
requires requires(W& w, T const& value) { w.scalar(value); }
55+
static constexpr void operator()(W& w, T const& value)
56+
{
57+
w.scalar(value);
58+
}
59+
60+
template<serializable_optional<format> OptionalT>
61+
static constexpr void operator()(W& w, OptionalT const& opt)
62+
{
63+
if (opt) {
64+
basic_save_fn{}(w, *opt);
65+
} else {
66+
w.null();
67+
}
68+
}
69+
70+
template<serializable_proxy<format> ProxyT>
71+
static constexpr void operator()(W& w, ProxyT const& proxy)
72+
{
73+
basic_save_fn{}(w, adapted_proxy_traits<ProxyT, format>::to_native_type(proxy));
74+
}
75+
76+
template<serializable_array<format> R>
77+
static constexpr void operator()(W& w, R const& arr)
78+
{
79+
w.begin_array();
80+
for (auto const& elem : arr) {
81+
basic_save_fn{}(w, elem);
82+
}
83+
w.end_array();
84+
}
85+
86+
template<serializable_map<format> MapT>
87+
static constexpr void operator()(W& w, MapT const& map)
88+
{
89+
w.begin_object();
90+
for (auto const& [k, v] : map) {
91+
if constexpr (adapted_proxy<ranges::range_key_t<MapT>, format>) {
92+
w.map_key(adapted_proxy_traits<ranges::range_key_t<MapT>, format>::to_native_type(k));
93+
} else {
94+
w.map_key(k);
95+
}
96+
basic_save_fn{}(w, v);
97+
}
98+
w.end_object();
99+
}
100+
101+
template<serializable_tuple<format> TupleT>
102+
static constexpr void operator()(W& w, TupleT const& tup)
103+
{
104+
w.begin_array();
105+
alloy::for_each(tup, [&](auto const& elem) {
106+
basic_save_fn{}(w, elem);
107+
});
108+
w.end_array();
109+
}
110+
111+
template<serializable_class<format> ClassT>
112+
static constexpr void operator()(W& w, ClassT const& klass)
113+
{
114+
w.begin_object();
115+
constexpr auto const& fields = adapted_class_traits<ClassT>::fields;
116+
alloy::for_each(fields, [&]<class T, auto GetMem, auto SetMem>(detail::field_definition<T, GetMem, SetMem> const& def) {
117+
if constexpr (adapted_proxy<decltype(def.name), format>) {
118+
w.map_key(adapted_proxy_traits<decltype(def.name), format>::to_native_type(def.name));
119+
} else {
120+
w.map_key(def.name);
121+
}
122+
basic_save_fn{}(w, (klass.*GetMem)());
123+
});
124+
w.end_object();
125+
}
126+
};
127+
128+
} // iris::marshal
129+
130+
#endif

include/iris/marshal/serialize_json.hpp

Lines changed: 42 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
#include <iris/config.hpp> // IWYU pragma: keep
77

8-
#include <iris/marshal/serialize_traits.hpp>
8+
#include <iris/marshal/serialize.hpp>
99
#include <iris/marshal/detail/field.hpp>
1010

1111
#include <iris/alloy/utility.hpp>
@@ -16,28 +16,13 @@
1616

1717
#include <nlohmann/json.hpp>
1818

19+
#include <vector>
1920
#include <string>
2021
#include <utility>
2122
#include <type_traits>
2223

2324
namespace iris::marshal::json {
2425

25-
namespace detail {
26-
27-
template<class T>
28-
struct native_t_impl
29-
{
30-
using type = T;
31-
};
32-
33-
template<StringLike T>
34-
struct native_t_impl<T>
35-
{
36-
using type = std::string;
37-
};
38-
39-
} // detail
40-
4126
struct format : generic_format
4227
{
4328
template<class K>
@@ -100,104 +85,70 @@ template<class T> concept deserializable_array = marshal::deserializable_arra
10085
template<class T> concept deserializable_tuple = marshal::deserializable_tuple<T, format>;
10186
template<class T> concept deserializable = marshal::deserializable<T, format>;
10287

103-
namespace detail {
104-
105-
struct save_fn
88+
class dom_writer
10689
{
107-
template<serializable_scalar T>
108-
static void operator()(nlohmann::json& out, T const& value)
109-
{
110-
if constexpr (std::is_enum_v<T>) {
111-
out = std::to_underlying(value);
112-
} else {
113-
out = value;
114-
}
115-
}
90+
nlohmann::json& root_;
91+
std::vector<nlohmann::json*> stack_;
92+
std::string pending_key_;
11693

117-
template<serializable_array R>
118-
static void operator()(nlohmann::json& out, R const& arr)
94+
[[nodiscard]] nlohmann::json& slot()
11995
{
120-
auto json_arr = nlohmann::json::array();
96+
if (stack_.empty()) return root_;
97+
auto& parent = *stack_.back();
98+
return parent.is_object() ? parent[pending_key_] : parent.emplace_back();
99+
}
121100

122-
for (auto const& elem : arr) {
123-
nlohmann::json elem_json;
124-
save_fn{}(elem_json, elem);
125-
json_arr.emplace_back(std::move(elem_json));
126-
}
101+
public:
102+
using format = json::format;
103+
using result_type = nlohmann::json;
127104

128-
out = std::move(json_arr);
129-
}
105+
explicit dom_writer(nlohmann::json& root)
106+
: root_(root)
107+
{}
130108

131-
template<serializable_map MapT>
132-
static void operator()(nlohmann::json& out, MapT const& map)
109+
template<class T>
110+
requires format::loadable_scalar<T>
111+
void scalar(T const& value)
133112
{
134-
auto json_map = nlohmann::json::object();
135-
136-
for (auto const& [key, value] : map) {
137-
if constexpr (adapted_proxy<ranges::range_key_t<MapT>, format>) {
138-
auto&& json_key = adapted_proxy_traits<ranges::range_key_t<MapT>, format>::to_native_type(key);
139-
save_fn{}(json_map[json_key], value);
140-
} else {
141-
save_fn{}(json_map[std::basic_string_view{key}], value);
142-
}
113+
if constexpr (std::is_enum_v<T>) {
114+
slot() = std::to_underlying(value);
115+
} else {
116+
slot() = value;
143117
}
144-
145-
out = std::move(json_map);
146118
}
147119

148-
template<serializable_tuple TupleT>
149-
static void operator()(nlohmann::json& out, TupleT const& tup)
120+
void null()
150121
{
151-
auto json_arr = nlohmann::json::array();
152-
153-
alloy::for_each(tup, [&](auto const& elem) {
154-
nlohmann::json elem_json;
155-
save_fn{}(elem_json, elem);
156-
json_arr.emplace_back(std::move(elem_json));
157-
});
158-
159-
out = std::move(json_arr);
122+
slot() = nullptr;
160123
}
161124

162-
template<serializable_class ClassT>
163-
static void operator()(nlohmann::json& out, ClassT const& klass)
125+
void begin_array()
164126
{
165-
auto json_map = nlohmann::json::object();
166-
167-
constexpr auto const& fields = adapted_class_traits<ClassT>::fields;
168-
alloy::for_each(fields, [&]<class T, auto GetMem, auto SetMem>(marshal::detail::field_definition<T, GetMem, SetMem> const& def) {
169-
save_fn{}(json_map[def.name], (klass.*GetMem)());
170-
});
171-
172-
out = std::move(json_map);
127+
stack_.push_back(&(slot() = nlohmann::json::array()));
173128
}
174-
175-
template<serializable_optional OptionalT>
176-
static void operator()(nlohmann::json& out, OptionalT const& opt)
129+
void end_array()
177130
{
178-
if (opt) {
179-
save_fn{}(out, *opt);
180-
} else {
181-
out = nullptr;
182-
}
131+
stack_.pop_back();
183132
}
184133

185-
template<serializable_proxy ProxyT>
186-
static void operator()(nlohmann::json& out, ProxyT const& proxy)
134+
void begin_object()
187135
{
188-
save_fn{}(out, adapted_proxy_traits<ProxyT, format>::to_native_type(proxy));
136+
stack_.push_back(&(slot() = nlohmann::json::object()));
189137
}
190-
191-
template<serializable T>
192-
[[nodiscard]] static nlohmann::json operator()(T const& value)
138+
void map_key(std::string_view k)
139+
{
140+
pending_key_.assign(k);
141+
}
142+
void end_object()
193143
{
194-
nlohmann::json out;
195-
save_fn{}(out, value);
196-
return out;
144+
stack_.pop_back();
197145
}
198146
};
199147

200-
// ----------------------------------------------------
148+
[[maybe_unused]] inline constexpr basic_save_fn<dom_writer> save{};
149+
150+
151+
namespace detail {
201152

202153
struct load_fn
203154
{
@@ -296,9 +247,6 @@ struct load_fn
296247

297248
} // detail
298249

299-
[[maybe_unused]] inline constexpr detail::save_fn save{};
300-
301-
302250
template<deserializable T>
303251
requires requires(nlohmann::json const& j) {
304252
detail::load_fn{}(j, std::declval<T&>());

include/iris/marshal/serialize_traits.hpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ template<class T, detail::marshal_format Format>
5656
struct adapted_proxy_traits;
5757

5858
template<class T, detail::marshal_format Format>
59-
using proxy_native_type_t = adapted_proxy_traits<std::remove_cvref_t<T>, Format>::native_type;
59+
using adapted_proxy_native_type_t = adapted_proxy_traits<std::remove_cvref_t<T>, Format>::native_type;
6060

6161
template<class T, class Format>
6262
concept adapted_proxy =
6363
detail::marshal_format<Format> &&
6464
requires(std::remove_cvref_t<T> const& v) {
65-
typename proxy_native_type_t<T, Format>;
65+
typename adapted_proxy_native_type_t<T, Format>;
6666
{ adapted_proxy_traits<std::remove_cvref_t<T>, Format>::to_native_type(v) }
67-
-> std::convertible_to<proxy_native_type_t<T, Format>>;
67+
-> std::convertible_to<adapted_proxy_native_type_t<T, Format>>;
6868
};
6969

7070
namespace detail {
@@ -73,7 +73,7 @@ template<class T, class Format>
7373
concept proxy_writable =
7474
detail::marshal_format<Format> &&
7575
adapted_proxy<T, Format> &&
76-
requires(proxy_native_type_t<T, Format> p) {
76+
requires(adapted_proxy_native_type_t<T, Format> p) {
7777
{ adapted_proxy_traits<std::remove_cvref_t<T>, Format>::from_native_type(std::move(p)) }
7878
-> std::convertible_to<std::remove_cvref_t<T>>;
7979
} &&
@@ -154,7 +154,7 @@ consteval bool is_serializable_impl()
154154
);
155155

156156
if constexpr (adapted_proxy<V, Format>) {
157-
return is_serializable_impl<proxy_native_type_t<V, Format>, Format>();
157+
return is_serializable_impl<adapted_proxy_native_type_t<V, Format>, Format>();
158158

159159
} else if constexpr (adapted_class<V>) {
160160
return true;
@@ -203,7 +203,7 @@ template<class T, class Format = generic_format>
203203
concept serializable_proxy =
204204
detail::marshal_format<Format> &&
205205
adapted_proxy<T, Format> &&
206-
detail::is_serializable_impl<proxy_native_type_t<T, Format>, Format>();
206+
detail::is_serializable_impl<adapted_proxy_native_type_t<T, Format>, Format>();
207207

208208
template<class T, class Format = generic_format>
209209
concept serializable_class =
@@ -298,7 +298,7 @@ consteval bool is_deserializable_impl()
298298
return false;
299299

300300
} else if constexpr (adapted_proxy<V, Format>) {
301-
return loadable<proxy_native_type_t<V, Format>, Format> && proxy_writable<V, Format>;
301+
return loadable<adapted_proxy_native_type_t<V, Format>, Format> && proxy_writable<V, Format>;
302302

303303
} else if constexpr (adapted_class<V>) {
304304
return true;

0 commit comments

Comments
 (0)