Skip to content

Commit 435c78c

Browse files
authored
Support a set of templates for converting classes to JSON (#1757)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent cf5b3da commit 435c78c

File tree

9 files changed

+484
-7
lines changed

9 files changed

+484
-7
lines changed

src/core/json/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME json
2-
PRIVATE_HEADERS array.h error.h object.h value.h hash.h
2+
PRIVATE_HEADERS array.h error.h object.h value.h hash.h auto.h
33
SOURCES grammar.h parser.h stringify.h json.cc json_value.cc)
44

55
if(SOURCEMETA_CORE_INSTALL)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#endif
77

88
// NOLINTBEGIN(misc-include-cleaner)
9+
#include <sourcemeta/core/json_auto.h>
910
#include <sourcemeta/core/json_error.h>
1011
#include <sourcemeta/core/json_value.h>
1112
// NOLINTEND(misc-include-cleaner)
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#ifndef SOURCEMETA_CORE_JSON_AUTO_H_
2+
#define SOURCEMETA_CORE_JSON_AUTO_H_
3+
4+
#include <sourcemeta/core/json_value.h>
5+
6+
#include <concepts> // std::same_as, std::constructible_from
7+
#include <functional> // std::function
8+
#include <optional> // std::optional
9+
#include <tuple> // std::tuple, std::apply
10+
#include <type_traits> // std::false_type, std::true_type, std::void_t, std::is_enum_v, std::underlying_type_t, std::is_same_v
11+
#include <utility> // std::pair
12+
13+
namespace sourcemeta::core {
14+
15+
/// @ingroup json
16+
template <typename, typename = void>
17+
struct to_json_has_mapped_type : std::false_type {};
18+
template <typename T>
19+
struct to_json_has_mapped_type<T, std::void_t<typename T::mapped_type>>
20+
: std::true_type {};
21+
22+
/// @ingroup json
23+
template <typename T> struct to_json_is_basic_string : std::false_type {};
24+
template <typename CharT, typename Traits, typename Alloc>
25+
struct to_json_is_basic_string<std::basic_string<CharT, Traits, Alloc>>
26+
: std::true_type {};
27+
28+
/// @ingroup json
29+
template <typename T>
30+
concept to_json_has_method = requires(const T value) {
31+
{ value.to_json() } -> std::same_as<JSON>;
32+
};
33+
34+
/// @ingroup json
35+
/// Container-like classes can opt-out from automatic JSON
36+
/// serialisation by setting `using json_auto = std::false_type;`
37+
template <typename, typename = void>
38+
struct to_json_supports_auto_impl : std::true_type {};
39+
template <typename T>
40+
struct to_json_supports_auto_impl<T, std::void_t<typename T::json_auto>>
41+
: std::bool_constant<
42+
!std::is_same_v<typename T::json_auto, std::false_type>> {};
43+
template <typename T>
44+
concept to_json_supports_auto = to_json_supports_auto_impl<T>::value;
45+
46+
/// @ingroup json
47+
template <typename T>
48+
concept to_json_list_like =
49+
requires(T type) {
50+
typename T::value_type;
51+
typename T::const_iterator;
52+
{ type.cbegin() } -> std::same_as<typename T::const_iterator>;
53+
{ type.cend() } -> std::same_as<typename T::const_iterator>;
54+
} && to_json_supports_auto<T> && !to_json_has_mapped_type<T>::value &&
55+
!to_json_has_method<T> && !to_json_is_basic_string<T>::value;
56+
57+
/// @ingroup json
58+
template <typename T>
59+
concept to_json_map_like =
60+
requires(T type) {
61+
typename T::value_type;
62+
typename T::const_iterator;
63+
typename T::key_type;
64+
{ type.cbegin() } -> std::same_as<typename T::const_iterator>;
65+
{ type.cend() } -> std::same_as<typename T::const_iterator>;
66+
} && to_json_supports_auto<T> && to_json_has_mapped_type<T>::value &&
67+
!to_json_has_method<T> &&
68+
std::is_same_v<typename T::key_type, JSON::String>;
69+
70+
/// @ingroup json
71+
/// If the value has a `.to_json()` method, always prefer that
72+
template <typename T>
73+
requires(to_json_has_method<T>)
74+
auto to_json(const T &value) -> JSON {
75+
return value.to_json();
76+
}
77+
78+
// TODO: How can we keep this in the hash header that does not yet know about
79+
// JSON?
80+
/// @ingroup json
81+
template <typename T>
82+
requires std::is_same_v<T, JSON::Object::Container::hash_type>
83+
auto to_json(const T &hash) -> JSON {
84+
auto result{JSON::make_array()};
85+
#if defined(__SIZEOF_INT128__)
86+
result.push_back(JSON{static_cast<std::size_t>(hash.a >> 64)});
87+
result.push_back(JSON{static_cast<std::size_t>(hash.a)});
88+
result.push_back(JSON{static_cast<std::size_t>(hash.b >> 64)});
89+
result.push_back(JSON{static_cast<std::size_t>(hash.b)});
90+
#else
91+
result.push_back(JSON{static_cast<std::size_t>(hash.a)});
92+
result.push_back(JSON{static_cast<std::size_t>(hash.b)});
93+
result.push_back(JSON{static_cast<std::size_t>(hash.c)});
94+
result.push_back(JSON{static_cast<std::size_t>(hash.d)});
95+
#endif
96+
return result;
97+
}
98+
99+
/// @ingroup json
100+
template <typename T>
101+
requires std::constructible_from<JSON, T>
102+
auto to_json(const T &value) -> JSON {
103+
return JSON{value};
104+
}
105+
106+
/// @ingroup json
107+
template <typename T>
108+
requires std::is_enum_v<T>
109+
auto to_json(const T value) -> JSON {
110+
return to_json<std::underlying_type_t<T>>(
111+
static_cast<std::underlying_type_t<T>>(value));
112+
}
113+
114+
/// @ingroup json
115+
template <typename T> auto to_json(const std::optional<T> &value) -> JSON {
116+
return value.has_value() ? to_json<T>(value.value()) : JSON{nullptr};
117+
}
118+
119+
/// @ingroup json
120+
template <to_json_list_like T>
121+
auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
122+
-> JSON {
123+
// TODO: Extend `make_array` to optionally take iterators, etc
124+
auto result{JSON::make_array()};
125+
for (auto iterator = begin; iterator != end; ++iterator) {
126+
result.push_back(to_json<typename T::value_type>(*iterator));
127+
}
128+
129+
return result;
130+
}
131+
132+
/// @ingroup json
133+
template <to_json_list_like T>
134+
auto to_json(
135+
typename T::const_iterator begin, typename T::const_iterator end,
136+
const std::function<JSON(const typename T::value_type &)> &callback)
137+
-> JSON {
138+
// TODO: Extend `make_array` to optionally take iterators, etc
139+
auto result{JSON::make_array()};
140+
for (auto iterator = begin; iterator != end; ++iterator) {
141+
result.push_back(callback(*iterator));
142+
}
143+
144+
return result;
145+
}
146+
147+
/// @ingroup json
148+
template <to_json_list_like T> auto to_json(const T &value) -> JSON {
149+
return to_json<T>(value.cbegin(), value.cend());
150+
}
151+
152+
/// @ingroup json
153+
template <to_json_list_like T>
154+
auto to_json(
155+
const T &value,
156+
const std::function<JSON(const typename T::value_type &)> &callback)
157+
-> JSON {
158+
return to_json<T>(value.cbegin(), value.cend(), callback);
159+
}
160+
161+
/// @ingroup json
162+
template <to_json_map_like T>
163+
auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
164+
-> JSON {
165+
auto result{JSON::make_object()};
166+
for (auto iterator = begin; iterator != end; ++iterator) {
167+
result.assign(iterator->first,
168+
to_json<typename T::mapped_type>(iterator->second));
169+
}
170+
171+
return result;
172+
}
173+
174+
/// @ingroup json
175+
template <to_json_map_like T> auto to_json(const T &value) -> JSON {
176+
return to_json<T>(value.cbegin(), value.cend());
177+
}
178+
179+
/// @ingroup json
180+
template <to_json_map_like T>
181+
auto to_json(
182+
typename T::const_iterator begin, typename T::const_iterator end,
183+
const std::function<JSON(const typename T::mapped_type &)> &callback)
184+
-> JSON {
185+
auto result{JSON::make_object()};
186+
for (auto iterator = begin; iterator != end; ++iterator) {
187+
result.assign(iterator->first, callback(iterator->second));
188+
}
189+
190+
return result;
191+
}
192+
193+
/// @ingroup json
194+
template <to_json_map_like T>
195+
auto to_json(
196+
const T &value,
197+
const std::function<JSON(const typename T::mapped_type &)> &callback)
198+
-> JSON {
199+
return to_json<T>(value.cbegin(), value.cend(), callback);
200+
}
201+
202+
/// @ingroup json
203+
template <typename L, typename R>
204+
auto to_json(const std::pair<L, R> &value) -> JSON {
205+
auto tuple{JSON::make_array()};
206+
tuple.push_back(to_json<L>(value.first));
207+
tuple.push_back(to_json<R>(value.second));
208+
return tuple;
209+
}
210+
211+
/// @ingroup json
212+
template <typename... Args>
213+
auto to_json(const std::tuple<Args...> &value) -> JSON {
214+
auto tuple{JSON::make_array()};
215+
std::apply(
216+
[&tuple](const Args &...elements) {
217+
(tuple.push_back(to_json(elements)), ...);
218+
},
219+
value);
220+
return tuple;
221+
}
222+
223+
} // namespace sourcemeta::core
224+
225+
#endif

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

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

20-
#include <functional> // std::reference_wrapper
21-
#include <memory> // std::allocator
22-
#include <ostream> // std::basic_ostream
23-
#include <string> // std::basic_string
20+
#include <functional> // std::reference_wrapper
21+
#include <memory> // std::allocator
22+
#include <ostream> // std::basic_ostream
23+
#include <string> // std::basic_string
24+
#include <type_traits> // std::is_same_v
2425

2526
/// @defgroup jsonpointer JSON Pointer
2627
/// @brief An growing implementation of RFC 6901 JSON Pointer.
@@ -565,6 +566,22 @@ using PointerWalker = GenericPointerWalker<Pointer>;
565566
/// ```
566567
using SubPointerWalker = GenericSubPointerWalker<Pointer>;
567568

569+
/// @ingroup jsonpointer
570+
/// Serialise a Pointer as JSON
571+
template <typename T>
572+
requires std::is_same_v<T, Pointer>
573+
auto to_json(const T &value) -> JSON {
574+
return JSON{to_string(value)};
575+
}
576+
577+
/// @ingroup jsonpointer
578+
/// Serialise a WeakPointer as JSON
579+
template <typename T>
580+
requires std::is_same_v<T, WeakPointer>
581+
auto to_json(const T &value) -> JSON {
582+
return JSON{to_string(value)};
583+
}
584+
568585
} // namespace sourcemeta::core
569586

570587
#endif

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include <functional> // std::reference_wrapper
1010
#include <initializer_list> // std::initializer_list
1111
#include <iterator> // std::advance, std::back_inserter
12-
#include <type_traits> // std::enable_if_t, std::is_same_v
12+
#include <type_traits> // std::enable_if_t, std::is_same_v, std::false_type
1313
#include <utility> // std::move
1414
#include <vector> // std::vector
1515

@@ -21,6 +21,8 @@ template <typename PropertyT, typename Hash> class GenericPointer {
2121
using Token = GenericToken<PropertyT, Hash>;
2222
using Value = typename Token::Value;
2323
using Container = std::vector<Token>;
24+
// We manually provide a JSON transformer
25+
using json_auto = std::false_type;
2426

2527
/// This constructor creates an empty JSON Pointer. For example:
2628
///

test/json/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT core NAME json
1616
json_string_test.cc
1717
json_stringify_test.cc
1818
json_value_test.cc
19-
json_type_test.cc)
19+
json_type_test.cc
20+
json_to_json_test.cc)
2021

2122
target_link_libraries(sourcemeta_core_json_unit
2223
PRIVATE sourcemeta::core::json)

0 commit comments

Comments
 (0)