Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
json_auto.h
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 <algorithm> // std::sort
7#include <bitset> // std::bitset
8#include <cassert> // assert
9#include <chrono> // std::chrono
10#include <concepts> // std::same_as, std::constructible_from, std::invocable, std::invocable
11#include <filesystem> // std::filesystem
12#include <functional> // std::function
13#include <optional> // std::optional, std::nullopt, std::bad_optional_access
14#include <tuple> // std::tuple, std::apply, std::tuple_element_t, std::tuple_size, std::tuple_size_v
15#include <type_traits> // std::false_type, std::true_type, std::is_enum_v, std::underlying_type_t, std::is_same_v, std::is_base_of_v, std::remove_cvref_t
16#include <utility> // std::pair, std::make_index_sequence, std::index_sequence, std::in_range
17#include <variant> // std::variant, std::variant_size_v, std::variant_alternative_t, std::visit
18
19namespace sourcemeta::core {
20
22template <typename T>
23concept json_auto_has_mapped_type = requires { typename T::mapped_type; };
24
26template <typename T> struct json_auto_is_basic_string : std::false_type {};
27template <typename CharT, typename Traits, typename Alloc>
28struct json_auto_is_basic_string<std::basic_string<CharT, Traits, Alloc>>
29 : std::true_type {};
30
32template <typename T>
33struct json_auto_is_basic_string_view : std::false_type {};
34template <typename CharT, typename Traits>
35struct json_auto_is_basic_string_view<std::basic_string_view<CharT, Traits>>
36 : std::true_type {};
37
39template <typename T> struct json_auto_is_bitset : std::false_type {};
40template <std::size_t N>
41struct json_auto_is_bitset<std::bitset<N>> : std::true_type {};
42
44template <typename T> struct json_auto_bitset_size;
45template <std::size_t N>
46struct json_auto_bitset_size<std::bitset<N>>
47 : std::integral_constant<std::size_t, N> {};
48
50template <typename T>
51concept json_auto_has_method_from = requires(const JSON &value) {
52 { T::from_json(value) } -> std::same_as<std::optional<T>>;
53};
54
56template <typename T>
57concept json_auto_has_method_to = requires(const T value) {
58 { value.to_json() } -> std::same_as<JSON>;
59};
60
64template <typename T>
65concept json_auto_supports_auto = !requires {
66 typename T::json_auto;
67} || !std::is_same_v<typename T::json_auto, std::false_type>;
68
70template <typename T>
72 requires(T type) {
73 typename T::value_type;
74 typename T::const_iterator;
75 { type.cbegin() } -> std::same_as<typename T::const_iterator>;
76 { type.cend() } -> std::same_as<typename T::const_iterator>;
81
83template <typename T>
85 requires(T type) {
86 typename T::value_type;
87 typename T::const_iterator;
88 typename T::key_type;
89 { type.cbegin() } -> std::same_as<typename T::const_iterator>;
90 { type.cend() } -> std::same_as<typename T::const_iterator>;
93 std::is_same_v<typename T::key_type, JSON::String>;
94
96template <typename T>
98 requires { typename T::reverse_iterator; };
99
101template <typename T> struct json_auto_is_pair : std::false_type {};
102
104template <typename U, typename V>
105struct json_auto_is_pair<std::pair<U, V>> : std::true_type {};
106
108template <typename T> struct json_auto_is_variant : std::false_type {};
109
111template <typename... Ts>
112struct json_auto_is_variant<std::variant<Ts...>> : std::true_type {};
113
115template <typename T>
116concept json_auto_tuple_mono = requires {
117 typename std::tuple_size<std::remove_cvref_t<T>>::type;
118} && (std::tuple_size_v<std::remove_cvref_t<T>> == 1);
119
120// We have to do this mess because MSVC seems confuses `std::pair`
121// of 2 elements with this overload
123template <typename T>
125 requires { typename std::tuple_size<std::remove_cvref_t<T>>::type; } &&
126 (std::tuple_size_v<std::remove_cvref_t<T>> >= 2) &&
127 (!std::is_base_of_v<
128 std::pair<std::tuple_element_t<0, std::remove_cvref_t<T>>,
129 std::tuple_element_t<1, std::remove_cvref_t<T>>>,
130 std::remove_cvref_t<T>>);
131
132// Forward declarations for recursive type conversions
133#ifndef DOXYGEN
134template <json_auto_list_like T> auto to_json(const T &value) -> JSON;
135template <json_auto_map_like T> auto to_json(const T &value) -> JSON;
136template <typename L, typename R>
137auto to_json(const std::pair<L, R> &value) -> JSON;
138template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON;
139template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON;
140template <typename T>
142auto to_json(const T &value) -> JSON;
143#endif
144
147template <typename T>
149auto to_json(const T &value) -> JSON {
150 return value.to_json();
151}
152
155template <typename T>
156 requires(json_auto_has_method_from<T>)
157auto from_json(const JSON &value) -> std::optional<T> {
158 return T::from_json(value);
159}
160
162template <typename T>
163 requires std::is_same_v<T, bool>
164auto from_json(const JSON &value) -> std::optional<T> {
165 if (value.is_boolean()) {
166 return value.to_boolean();
167 } else {
168 return std::nullopt;
169 }
170}
171
173template <typename T>
174 requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
175auto from_json(const JSON &value) -> std::optional<T> {
176 // A value outside the target type's range yields no result rather than a
177 // silently narrowed one
178 if (value.is_integer() && std::in_range<T>(value.to_integer())) {
179 return static_cast<T>(value.to_integer());
180 } else {
181 return std::nullopt;
182 }
183}
184
186template <typename T>
187 requires std::is_same_v<T, Decimal>
188auto from_json(const JSON &value) -> std::optional<T> {
189 if (value.is_decimal()) {
190 return value.to_decimal();
191 } else {
192 return std::nullopt;
193 }
194}
195
196// TODO: How can we keep this in the hash header that does not yet know about
197// JSON?
199template <typename T>
200 requires std::is_same_v<T, JSON::Object::hash_type>
201auto to_json(const T &hash) -> JSON {
202 auto result{JSON::make_array()};
203 result.push_back(JSON{static_cast<std::size_t>(hash.a >> 64)});
204 result.push_back(JSON{static_cast<std::size_t>(hash.a)});
205 result.push_back(JSON{static_cast<std::size_t>(hash.b >> 64)});
206 result.push_back(JSON{static_cast<std::size_t>(hash.b)});
207 return result;
208}
209
210// TODO: How can we keep this in the hash header that does not yet know about
211// JSON?
213template <typename T>
214 requires std::is_same_v<T, JSON::Object::hash_type>
215auto from_json(const JSON &value) -> std::optional<T> {
216 if (!value.is_array() || value.size() != 4 || !value.at(0).is_integer() ||
217 !value.at(1).is_integer() || !value.at(2).is_integer() ||
218 !value.at(3).is_integer()) {
219 return std::nullopt;
220 }
221
222 using uint128_type = JSON::Object::hash_type::type;
223 return T{(static_cast<uint128_type>(
224 static_cast<std::uint64_t>(value.at(0).to_integer()))
225 << 64) |
226 static_cast<uint128_type>(
227 static_cast<std::uint64_t>(value.at(1).to_integer())),
228 (static_cast<uint128_type>(
229 static_cast<std::uint64_t>(value.at(2).to_integer()))
230 << 64) |
231 static_cast<uint128_type>(
232 static_cast<std::uint64_t>(value.at(3).to_integer()))};
233}
234
236template <typename T>
237 requires(std::constructible_from<JSON, T> &&
238 // Otherwise MSVC gets confused
239 !std::is_same_v<T, unsigned long long>)
240auto to_json(const T &value) -> JSON {
241 return JSON{value};
242}
243
246template <typename T>
247 requires std::is_same_v<T, std::filesystem::file_time_type>
248auto to_json(const T value) -> JSON {
249 return JSON{static_cast<std::int64_t>(value.time_since_epoch().count())};
250}
251
253template <typename T>
254 requires std::is_same_v<T, std::filesystem::file_time_type>
255auto from_json(const JSON &value) -> std::optional<T> {
256 if (value.is_integer()) {
257 using file_time_type = std::filesystem::file_time_type;
258 return file_time_type{file_time_type::duration{
259 static_cast<file_time_type::duration::rep>(value.to_integer())}};
260 } else {
261 return std::nullopt;
262 }
263}
264
266template <typename T>
267 requires(std::is_same_v<T, std::filesystem::path> &&
268 // In at least Clang and GCC, paths are convertible to strings,
269 // resulting in ambiguous templated calls
270 !std::is_convertible_v<T, std::string>)
271auto to_json(const T value) -> JSON {
272 return JSON{value.string()};
273}
274
276template <typename T>
277 requires std::is_same_v<T, std::filesystem::path>
278auto from_json(const JSON &value) -> std::optional<T> {
279 if (value.is_string()) {
280 return value.to_string();
281 } else {
282 return std::nullopt;
283 }
284}
285
287template <typename T>
289auto to_json(const T &bitset) -> JSON {
290 constexpr std::size_t N{json_auto_bitset_size<T>::value};
291 if constexpr (N <= 64) {
292 return JSON{static_cast<std::int64_t>(bitset.to_ullong())};
293 } else {
294 return JSON{bitset.to_string()};
295 }
296}
297
299template <typename T>
301auto from_json(const JSON &value) -> std::optional<T> {
302 constexpr std::size_t N{json_auto_bitset_size<T>::value};
303 if constexpr (N <= 64) {
304 if (value.is_integer()) {
305 return T{static_cast<unsigned long long>(value.to_integer())};
306 } else {
307 return std::nullopt;
308 }
309 } else {
310 if (value.is_string()) {
311 return T{value.to_string()};
312 } else {
313 return std::nullopt;
314 }
315 }
316}
317
319template <typename T>
320 requires std::is_same_v<T, unsigned long long>
321auto to_json(const T value) -> JSON {
322 return JSON{static_cast<std::int64_t>(value)};
323}
324
326template <typename T>
327 requires std::is_same_v<T, JSON>
328auto from_json(const JSON &value) -> std::optional<T> {
329 return value;
330}
331
333template <typename T>
335auto from_json(const JSON &value) -> std::optional<T> {
336 if (value.is_string()) {
337 return value.to_string();
338 } else {
339 return std::nullopt;
340 }
341}
342
344template <typename T>
345 requires std::is_enum_v<T>
346auto to_json(const T value) -> JSON {
347 return to_json(static_cast<std::underlying_type_t<T>>(value));
348}
349
351template <typename T>
352 requires std::is_enum_v<T>
353auto from_json(const JSON &value) -> std::optional<T> {
354 if (value.is_integer()) {
355 return static_cast<T>(value.to_integer());
356 } else {
357 return std::nullopt;
358 }
359}
360
363template <typename T> auto to_json(const std::optional<T> &value) -> JSON {
364 return value.has_value() ? to_json(value.value()) : JSON{nullptr};
365}
366
368template <typename T>
369 requires requires { typename T::value_type; } &&
370 std::is_same_v<T, std::optional<typename T::value_type>>
371auto from_json(const JSON &value) -> std::optional<T> {
372 if (value.is_null()) {
373 return std::optional<T>{
374 std::optional<typename T::value_type>{std::nullopt}};
375 } else {
376 auto result{from_json<typename T::value_type>(value)};
377 if (!result.has_value()) {
378 return std::nullopt;
379 }
380
381 return result;
382 }
383}
384
387template <json_auto_list_like T>
388auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
389 -> JSON {
390 // TODO: Extend `make_array` to optionally take iterators, etc
391 auto result{JSON::make_array()};
392 for (auto iterator = begin; iterator != end; ++iterator) {
393 result.push_back(to_json(*iterator));
394 }
395
396 // To guarantee ordering across implementations
397 if constexpr (!json_auto_has_reverse_iterator<T>) {
398 std::sort(result.as_array().begin(), result.as_array().end());
399 }
400
401 return result;
402}
403
407template <json_auto_list_like T,
408 std::invocable<const typename T::value_type &> F>
409auto to_json(typename T::const_iterator begin, typename T::const_iterator end,
410 const F &callback) -> JSON {
411 // TODO: Extend `make_array` to optionally take iterators, etc
412 auto result{JSON::make_array()};
413 for (auto iterator = begin; iterator != end; ++iterator) {
414 result.push_back(callback(*iterator));
415 }
416
417 // To guarantee ordering across implementations
418 if constexpr (!json_auto_has_reverse_iterator<T>) {
419 std::sort(result.as_array().begin(), result.as_array().end());
420 }
421
422 return result;
423}
424
426template <json_auto_list_like T> auto to_json(const T &value) -> JSON {
427 return to_json<T>(value.cbegin(), value.cend());
428}
429
432template <json_auto_list_like T,
433 std::invocable<const typename T::value_type &> F>
434auto to_json(const T &value, const F &callback) -> JSON {
435 return to_json<T>(value.cbegin(), value.cend(), callback);
436}
437
439template <json_auto_list_like T>
440auto from_json(const JSON &value) -> std::optional<T> {
441 if (!value.is_array()) {
442 return std::nullopt;
443 }
444
445 T result;
446
447 if constexpr (requires { result.reserve(value.size()); }) {
448 result.reserve(value.size());
449 }
450
451 for (const auto &item : value.as_array()) {
452 auto subvalue{from_json<typename T::value_type>(item)};
453 if (!subvalue.has_value()) {
454 return std::nullopt;
455 }
456
457 if constexpr (requires { result.insert(subvalue.value()); }) {
458 result.insert(std::move(subvalue).value());
459 } else {
460 result.push_back(std::move(subvalue).value());
461 }
462 }
463
464 return result;
465}
466
469template <json_auto_list_like T>
471 const JSON &value,
472 const std::function<std::optional<typename T::value_type>(const JSON &)>
473 &callback) -> std::optional<T> {
474 if (!value.is_array()) {
475 return std::nullopt;
476 }
477
478 T result;
479
480 if constexpr (requires { result.reserve(value.size()); }) {
481 result.reserve(value.size());
482 }
483
484 for (const auto &item : value.as_array()) {
485 auto subvalue{callback(item)};
486 if (!subvalue.has_value()) {
487 return std::nullopt;
488 }
489
490 if constexpr (requires { result.insert(subvalue.value()); }) {
491 result.insert(std::move(subvalue).value());
492 } else {
493 result.push_back(std::move(subvalue).value());
494 }
495 }
496
497 return result;
498}
499
501template <json_auto_map_like T>
502auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
503 -> JSON {
504 auto result{JSON::make_object()};
505 for (auto iterator = begin; iterator != end; ++iterator) {
506 result.assign(iterator->first, to_json(iterator->second));
507 }
508
509 return result;
510}
511
513template <json_auto_map_like T> auto to_json(const T &value) -> JSON {
514 return to_json<T>(value.cbegin(), value.cend());
515}
516
518template <json_auto_map_like T,
519 std::invocable<const typename T::mapped_type &> F>
520auto to_json(typename T::const_iterator begin, typename T::const_iterator end,
521 const F &callback) -> JSON {
522 auto result{JSON::make_object()};
523 for (auto iterator = begin; iterator != end; ++iterator) {
524 result.assign(iterator->first, callback(iterator->second));
525 }
526
527 return result;
528}
529
531template <json_auto_map_like T>
532auto from_json(const JSON &value) -> std::optional<T> {
533 if (!value.is_object()) {
534 return std::nullopt;
535 }
536
537 T result;
538 for (const auto &item : value.as_object()) {
539 auto subvalue{from_json<typename T::mapped_type>(item.second)};
540 if (!subvalue.has_value()) {
541 return std::nullopt;
542 }
543
544 result.emplace(item.first, std::move(subvalue).value());
545 }
546
547 return result;
548}
549
552template <json_auto_map_like T>
554 const JSON &value,
555 const std::function<std::optional<typename T::mapped_type>(const JSON &)>
556 &callback) -> std::optional<T> {
557 if (!value.is_object()) {
558 return std::nullopt;
559 }
560
561 T result;
562 for (const auto &item : value.as_object()) {
563 auto subvalue{callback(item.second)};
564 if (!subvalue.has_value()) {
565 return std::nullopt;
566 }
567
568 result.emplace(item.first, std::move(subvalue).value());
569 }
570
571 return result;
572}
573
575template <json_auto_map_like T,
576 std::invocable<const typename T::mapped_type &> F>
577auto to_json(const T &value, const F &callback) -> JSON {
578 return to_json<T>(value.cbegin(), value.cend(), callback);
579}
580
583template <typename L, typename R>
584auto to_json(const std::pair<L, R> &value) -> JSON {
585 auto tuple{JSON::make_array()};
586 tuple.push_back(to_json(value.first));
587 tuple.push_back(to_json(value.second));
588 return tuple;
589}
590
592template <typename T>
593 requires json_auto_is_pair<T>::value
594auto from_json(const JSON &value) -> std::optional<T> {
595 if (!value.is_array() || value.size() != 2) {
596 return std::nullopt;
597 }
598
599 auto first{from_json<typename T::first_type>(value.at(0))};
600 auto second{from_json<typename T::second_type>(value.at(1))};
601 if (!first.has_value() || !second.has_value()) {
602 return std::nullopt;
603 }
604
605 return std::make_pair<typename T::first_type, typename T::second_type>(
606 std::move(first).value(), std::move(second).value());
607}
608
609// Handle 1-element tuples
611template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON {
612 auto tuple = JSON::make_array();
613 std::apply(
614 [&](const auto &element) -> auto { tuple.push_back(to_json(element)); },
615 value);
616 return tuple;
617}
618
620template <json_auto_tuple_mono T>
621auto from_json(const JSON &value) -> std::optional<T> {
622 if (!value.is_array() || value.size() != 1) {
623 return std::nullopt;
624 }
625
626 auto first{from_json<std::tuple_element_t<0, T>>(value.at(0))};
627 if (!first.has_value()) {
628 return std::nullopt;
629 }
630
631 return {std::move(first).value()};
632}
633
635template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON {
636 auto tuple = JSON::make_array();
637 std::apply(
638 [&tuple](const auto &...elements) -> auto {
639 (tuple.push_back(to_json(elements)), ...);
640 },
641 value);
642 return tuple;
643}
644
645#ifndef DOXYGEN
646template <typename T, std::size_t... Indices>
647auto from_json_tuple_poly(const JSON &value, std::index_sequence<Indices...>)
648 -> T {
649 return {from_json<std::tuple_element_t<Indices, T>>(value.at(Indices))
650 .value()...};
651}
652#endif
653
655template <json_auto_tuple_poly T>
656auto from_json(const JSON &value) -> std::optional<T> {
657 if (!value.is_array() || value.size() != std::tuple_size_v<T>) {
658 return std::nullopt;
659 }
660
661 try {
662 return from_json_tuple_poly<T>(
663 value, std::make_index_sequence<std::tuple_size_v<T>>{});
664 // TODO: Maybe there is a better way to catch this without using exceptions?
665 } catch (const std::bad_optional_access &) {
666 return std::nullopt;
667 }
668}
669
671template <typename T>
673auto to_json(const T &value) -> JSON {
674 auto result{JSON::make_array()};
675 result.push_back(JSON{static_cast<std::int64_t>(value.index())});
676 std::visit(
677 [&result](const auto &alternative) -> auto {
678 result.push_back(to_json(alternative));
679 },
680 value);
681 return result;
682}
683
684#ifndef DOXYGEN
685template <typename T, std::size_t Index = 0>
686auto from_json_variant_impl(const JSON &data, std::size_t index)
687 -> std::optional<T> {
688 if constexpr (Index >= std::variant_size_v<T>) {
689 return std::nullopt;
690 } else {
691 if (Index == index) {
693 if (result.has_value()) {
694 return T{std::in_place_index<Index>, std::move(result).value()};
695 }
696
697 return std::nullopt;
698 } else {
699 return from_json_variant_impl<T, Index + 1>(data, index);
700 }
701 }
702}
703#endif
704
706template <typename T>
708auto from_json(const JSON &value) -> std::optional<T> {
709 if (!value.is_array() || value.size() != 2 || !value.at(0).is_integer()) {
710 return std::nullopt;
711 }
712
713 const auto index{static_cast<std::size_t>(value.at(0).to_integer())};
714 if (index >= std::variant_size_v<T>) {
715 return std::nullopt;
716 } else {
717 return from_json_variant_impl<T>(value.at(1), index);
718 }
719}
720
721} // namespace sourcemeta::core
722
723#endif
Definition json_auto.h:71
Definition json_auto.h:84
Definition json_auto.h:116
Definition json_auto.h:124
SOURCEMETA_FORCEINLINE auto as_array() const noexcept -> const Array &
Definition json_value.h:745
SOURCEMETA_FORCEINLINE auto to_boolean() const noexcept -> bool
Definition json_value.h:626
SOURCEMETA_FORCEINLINE auto as_object() noexcept -> Object &
Definition json_value.h:791
static auto make_array() -> JSON
SOURCEMETA_FORCEINLINE auto is_object() const noexcept -> bool
Definition json_value.h:576
static auto make_object() -> JSON
SOURCEMETA_FORCEINLINE auto is_boolean() const noexcept -> bool
Definition json_value.h:433
SOURCEMETA_FORCEINLINE auto is_array() const noexcept -> bool
Definition json_value.h:561
static auto size(const String &value) noexcept -> std::size_t
SOURCEMETA_FORCEINLINE auto at(const typename Array::size_type index) const -> const JSON &
Definition json_value.h:904
Definition json_value.h:39
auto to_json(const T &value) -> JSON
Definition json_auto.h:149
auto from_json(const JSON &value) -> std::optional< T >
Definition json_auto.h:157
Definition json_auto.h:39
Definition json_auto.h:101
Definition json_auto.h:108
@ Index
A map of index labels carrying no RDF ranging over an object.
Definition jsonld_materialize.h:116