Skip to content

Commit d7199d5

Browse files
authored
Implement from_json template support in json_auto.h (#1785)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 66bc02f commit d7199d5

File tree

6 files changed

+326
-35
lines changed

6 files changed

+326
-35
lines changed

src/core/json/include/sourcemeta/core/json_auto.h

Lines changed: 207 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
#include <sourcemeta/core/json_value.h>
55

66
#include <algorithm> // std::sort
7+
#include <cassert> // assert
78
#include <concepts> // std::same_as, std::constructible_from
89
#include <functional> // std::function
910
#include <optional> // std::optional
1011
#include <tuple> // std::tuple, std::apply, std::tuple_element_t, std::tuple_size, std::tuple_size_v
1112
#include <type_traits> // std::false_type, std::true_type, std::void_t, std::is_enum_v, std::underlying_type_t, std::is_same_v, std::is_base_of_v, std::remove_cvref_t
12-
#include <utility> // std::pair
13+
#include <utility> // std::pair, std:::make_index_sequence, std::index_sequence
1314

1415
// Forward declarations (added as needed)
1516
#ifndef DOXYGEN
@@ -36,7 +37,13 @@ struct json_auto_is_basic_string<std::basic_string<CharT, Traits, Alloc>>
3637

3738
/// @ingroup json
3839
template <typename T>
39-
concept json_auto_has_method = requires(const T value) {
40+
concept json_auto_has_method_from = requires(const JSON &value) {
41+
{ T::from_json(value) } -> std::same_as<T>;
42+
};
43+
44+
/// @ingroup json
45+
template <typename T>
46+
concept json_auto_has_method_to = requires(const T value) {
4047
{ value.to_json() } -> std::same_as<JSON>;
4148
};
4249

@@ -61,7 +68,8 @@ concept json_auto_list_like =
6168
{ type.cbegin() } -> std::same_as<typename T::const_iterator>;
6269
{ type.cend() } -> std::same_as<typename T::const_iterator>;
6370
} && json_auto_supports_auto<T> && !json_auto_has_mapped_type<T>::value &&
64-
!json_auto_has_method<T> && !json_auto_is_basic_string<T>::value;
71+
!json_auto_has_method_from<T> && !json_auto_has_method_to<T> &&
72+
!json_auto_is_basic_string<T>::value;
6573

6674
/// @ingroup json
6775
template <typename T>
@@ -73,7 +81,7 @@ concept json_auto_map_like =
7381
{ type.cbegin() } -> std::same_as<typename T::const_iterator>;
7482
{ type.cend() } -> std::same_as<typename T::const_iterator>;
7583
} && json_auto_supports_auto<T> && json_auto_has_mapped_type<T>::value &&
76-
!json_auto_has_method<T> &&
84+
!json_auto_has_method_from<T> && !json_auto_has_method_to<T> &&
7785
std::is_same_v<typename T::key_type, JSON::String>;
7886

7987
/// @ingroup json
@@ -86,6 +94,13 @@ struct json_auto_has_reverse_iterator<T,
8694
std::void_t<typename T::reverse_iterator>>
8795
: std::true_type {};
8896

97+
/// @ingroup json
98+
template <typename T> struct json_auto_is_pair : std::false_type {};
99+
100+
/// @ingroup json
101+
template <typename U, typename V>
102+
struct json_auto_is_pair<std::pair<U, V>> : std::true_type {};
103+
89104
/// @ingroup json
90105
template <typename T>
91106
concept json_auto_tuple_mono = requires {
@@ -107,11 +122,35 @@ concept json_auto_tuple_poly =
107122
/// @ingroup json
108123
/// If the value has a `.to_json()` method, always prefer that
109124
template <typename T>
110-
requires(json_auto_has_method<T>)
125+
requires(json_auto_has_method_to<T>)
111126
auto to_json(const T &value) -> JSON {
112127
return value.to_json();
113128
}
114129

130+
/// @ingroup json
131+
/// If the value has a `.from_json()` static method, always prefer that
132+
template <typename T>
133+
requires(json_auto_has_method_from<T>)
134+
auto from_json(const JSON &value) -> T {
135+
return T::from_json(value);
136+
}
137+
138+
/// @ingroup json
139+
template <typename T>
140+
requires std::is_same_v<T, bool>
141+
auto from_json(const JSON &value) -> T {
142+
assert(value.is_boolean());
143+
return value.to_boolean();
144+
}
145+
146+
/// @ingroup json
147+
template <typename T>
148+
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
149+
auto from_json(const JSON &value) -> T {
150+
assert(value.is_integer());
151+
return static_cast<T>(value.to_integer());
152+
}
153+
115154
// TODO: How can we keep this in the hash header that does not yet know about
116155
// JSON?
117156
/// @ingroup json
@@ -133,25 +172,83 @@ auto to_json(const T &hash) -> JSON {
133172
return result;
134173
}
135174

175+
// TODO: How can we keep this in the hash header that does not yet know about
176+
// JSON?
177+
/// @ingroup json
178+
template <typename T>
179+
requires std::is_same_v<T, JSON::Object::Container::hash_type>
180+
auto from_json(const JSON &value) -> T {
181+
assert(value.is_array());
182+
assert(value.size() == 4);
183+
#if defined(__SIZEOF_INT128__)
184+
return {
185+
(static_cast<__uint128_t>(from_json<std::uint64_t>(value.at(0))) << 64) |
186+
from_json<std::uint64_t>(value.at(1)),
187+
(static_cast<__uint128_t>(from_json<std::uint64_t>(value.at(2))) << 64) |
188+
from_json<std::uint64_t>(value.at(3))};
189+
#else
190+
return {from_json<std::uint64_t>(value.at(0)),
191+
from_json<std::uint64_t>(value.at(1)),
192+
from_json<std::uint64_t>(value.at(2)),
193+
from_json<std::uint64_t>(value.at(3))};
194+
#endif
195+
}
196+
136197
/// @ingroup json
137198
template <typename T>
138199
requires std::constructible_from<JSON, T>
139200
auto to_json(const T &value) -> JSON {
140201
return JSON{value};
141202
}
142203

204+
/// @ingroup json
205+
template <typename T>
206+
requires std::is_same_v<T, JSON>
207+
auto from_json(const JSON &value) -> T {
208+
return value;
209+
}
210+
211+
/// @ingroup json
212+
template <typename T>
213+
requires json_auto_is_basic_string<T>::value
214+
auto from_json(const JSON &value) -> T {
215+
assert(value.is_string());
216+
return value.to_string();
217+
}
218+
143219
/// @ingroup json
144220
template <typename T>
145221
requires std::is_enum_v<T>
146222
auto to_json(const T value) -> JSON {
147223
return to_json(static_cast<std::underlying_type_t<T>>(value));
148224
}
149225

226+
/// @ingroup json
227+
template <typename T>
228+
requires std::is_enum_v<T>
229+
auto from_json(const JSON &value) -> T {
230+
assert(value.is_integer());
231+
assert(value.is_positive());
232+
return static_cast<T>(value.to_integer());
233+
}
234+
150235
/// @ingroup json
151236
template <typename T> auto to_json(const std::optional<T> &value) -> JSON {
152237
return value.has_value() ? to_json(value.value()) : JSON{nullptr};
153238
}
154239

240+
/// @ingroup json
241+
template <typename T>
242+
requires requires { typename T::value_type; } &&
243+
std::is_same_v<T, std::optional<typename T::value_type>>
244+
auto from_json(const JSON &value) -> T {
245+
if (value.is_null()) {
246+
return {};
247+
} else {
248+
return from_json<typename T::value_type>(value);
249+
}
250+
}
251+
155252
/// @ingroup json
156253
template <json_auto_list_like T>
157254
auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
@@ -204,6 +301,52 @@ auto to_json(
204301
return to_json<T>(value.cbegin(), value.cend(), callback);
205302
}
206303

304+
/// @ingroup json
305+
template <json_auto_list_like T> auto from_json(const JSON &value) -> T {
306+
assert(value.is_array());
307+
T result;
308+
309+
if constexpr (requires { result.reserve(value.size()); }) {
310+
result.reserve(value.size());
311+
}
312+
313+
for (const auto &item : value.as_array()) {
314+
if constexpr (requires {
315+
result.insert(from_json<typename T::value_type>(item));
316+
}) {
317+
result.insert(from_json<typename T::value_type>(item));
318+
} else {
319+
result.push_back(from_json<typename T::value_type>(item));
320+
}
321+
}
322+
323+
return result;
324+
}
325+
326+
template <json_auto_list_like T>
327+
auto from_json(
328+
const JSON &value,
329+
const std::function<typename T::value_type(const JSON &)> &callback) -> T {
330+
assert(value.is_array());
331+
T result;
332+
333+
if constexpr (requires { result.reserve(value.size()); }) {
334+
result.reserve(value.size());
335+
}
336+
337+
for (const auto &item : value.as_array()) {
338+
if constexpr (requires {
339+
result.insert(from_json<typename T::value_type>(item));
340+
}) {
341+
result.insert(callback(item));
342+
} else {
343+
result.push_back(callback(item));
344+
}
345+
}
346+
347+
return result;
348+
}
349+
207350
/// @ingroup json
208351
template <json_auto_map_like T>
209352
auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
@@ -235,6 +378,31 @@ auto to_json(
235378
return result;
236379
}
237380

381+
/// @ingroup json
382+
template <json_auto_map_like T> auto from_json(const JSON &value) -> T {
383+
assert(value.is_object());
384+
T result;
385+
for (const auto &item : value.as_object()) {
386+
result.emplace(item.first, from_json<typename T::mapped_type>(item.second));
387+
}
388+
389+
return result;
390+
}
391+
392+
/// @ingroup json
393+
template <json_auto_map_like T>
394+
auto from_json(
395+
const JSON &value,
396+
const std::function<typename T::mapped_type(const JSON &)> &callback) -> T {
397+
assert(value.is_object());
398+
T result;
399+
for (const auto &item : value.as_object()) {
400+
result.emplace(item.first, callback(item.second));
401+
}
402+
403+
return result;
404+
}
405+
238406
/// @ingroup json
239407
template <json_auto_map_like T>
240408
auto to_json(
@@ -253,6 +421,17 @@ auto to_json(const std::pair<L, R> &value) -> JSON {
253421
return tuple;
254422
}
255423

424+
/// @ingroup json
425+
template <typename T>
426+
requires json_auto_is_pair<T>::value
427+
auto from_json(const JSON &value) -> T {
428+
assert(value.is_array());
429+
assert(value.size() == 2);
430+
return std::make_pair<typename T::first_type, typename T::second_type>(
431+
from_json<typename T::first_type>(value.at(0)),
432+
from_json<typename T::second_type>(value.at(1)));
433+
}
434+
256435
// Handle 1-element tuples
257436
/// @ingroup json
258437
template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON {
@@ -262,6 +441,13 @@ template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON {
262441
return tuple;
263442
}
264443

444+
/// @ingroup json
445+
template <json_auto_tuple_mono T> auto from_json(const JSON &value) -> T {
446+
assert(value.is_array());
447+
assert(value.size() == 1);
448+
return {from_json<std::tuple_element_t<0, T>>(value.at(0))};
449+
}
450+
265451
/// @ingroup json
266452
template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON {
267453
auto tuple = JSON::make_array();
@@ -273,6 +459,22 @@ template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON {
273459
return tuple;
274460
}
275461

462+
#ifndef DOXYGEN
463+
template <typename T, std::size_t... Indices>
464+
auto from_json_tuple_poly(const JSON &value, std::index_sequence<Indices...>)
465+
-> T {
466+
return {from_json<std::tuple_element_t<Indices, T>>(value.at(Indices))...};
467+
}
468+
#endif
469+
470+
/// @ingroup json
471+
template <json_auto_tuple_poly T> auto from_json(const JSON &value) -> T {
472+
assert(value.is_array());
473+
assert(value.size() == std::tuple_size_v<T>);
474+
return from_json_tuple_poly<T>(
475+
value, std::make_index_sequence<std::tuple_size_v<T>>{});
476+
}
477+
276478
} // namespace sourcemeta::core
277479

278480
#endif

src/core/jsonpointer/include/sourcemeta/core/jsonpointer.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <sourcemeta/core/jsonpointer_walker.h>
1818
// NOLINTEND(misc-include-cleaner)
1919

20+
#include <cassert> // assert
2021
#include <functional> // std::reference_wrapper
2122
#include <memory> // std::allocator
2223
#include <ostream> // std::basic_ostream
@@ -582,6 +583,15 @@ auto to_json(const T &value) -> JSON {
582583
return JSON{to_string(value)};
583584
}
584585

586+
/// @ingroup jsonpointer
587+
/// Deserialise a Pointer from JSON
588+
template <typename T>
589+
requires std::is_same_v<T, Pointer>
590+
auto from_json(const JSON &value) -> T {
591+
assert(value.is_string());
592+
return to_pointer(value.to_string());
593+
}
594+
585595
} // namespace sourcemeta::core
586596

587597
#endif

test/json/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME json
1717
json_stringify_test.cc
1818
json_value_test.cc
1919
json_type_test.cc
20-
json_to_json_test.cc)
20+
json_auto_test.cc)
2121

2222
target_link_libraries(sourcemeta_core_json_unit
2323
PRIVATE sourcemeta::core::json)

0 commit comments

Comments
 (0)