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 JSONAutoIsBasicString : std::false_type {};
27template <typename CharT, typename Traits, typename Alloc>
28struct JSONAutoIsBasicString<std::basic_string<CharT, Traits, Alloc>>
29 : std::true_type {};
30
32template <typename T> struct JSONAutoIsBasicStringView : std::false_type {};
33template <typename CharT, typename Traits>
34struct JSONAutoIsBasicStringView<std::basic_string_view<CharT, Traits>>
35 : std::true_type {};
36
38template <typename T> struct JSONAutoIsBitset : std::false_type {};
39template <std::size_t N>
40struct JSONAutoIsBitset<std::bitset<N>> : std::true_type {};
41
43template <typename T> struct json_auto_bitset_size;
44template <std::size_t N>
45struct json_auto_bitset_size<std::bitset<N>>
46 : std::integral_constant<std::size_t, N> {};
47
49template <typename T>
50concept json_auto_has_method_from = requires(const JSON &value) {
51 { T::from_json(value) } -> std::same_as<std::optional<T>>;
52};
53
55template <typename T>
56concept json_auto_has_method_to = requires(const T value) {
57 { value.to_json() } -> std::same_as<JSON>;
58};
59
63template <typename T>
64concept json_auto_supports_auto = !requires {
65 typename T::json_auto;
66} || !std::is_same_v<typename T::json_auto, std::false_type>;
67
69template <typename T>
71 requires(T type) {
72 typename T::value_type;
73 typename T::const_iterator;
74 { type.cbegin() } -> std::same_as<typename T::const_iterator>;
75 { type.cend() } -> std::same_as<typename T::const_iterator>;
79
81template <typename T>
83 requires(T type) {
84 typename T::value_type;
85 typename T::const_iterator;
86 typename T::key_type;
87 { type.cbegin() } -> std::same_as<typename T::const_iterator>;
88 { type.cend() } -> std::same_as<typename T::const_iterator>;
91 std::is_same_v<typename T::key_type, JSON::String>;
92
94template <typename T>
96 requires { typename T::reverse_iterator; };
97
99template <typename T> struct JSONAutoIsPair : std::false_type {};
100
102template <typename U, typename V>
103struct JSONAutoIsPair<std::pair<U, V>> : std::true_type {};
104
106template <typename T> struct JSONAutoIsVariant : std::false_type {};
107
109template <typename... Ts>
110struct JSONAutoIsVariant<std::variant<Ts...>> : std::true_type {};
111
113template <typename T>
114concept json_auto_tuple_mono = requires {
115 typename std::tuple_size<std::remove_cvref_t<T>>::type;
116} && (std::tuple_size_v<std::remove_cvref_t<T>> == 1);
117
118// We have to do this mess because MSVC seems confuses `std::pair`
119// of 2 elements with this overload
121template <typename T>
123 requires { typename std::tuple_size<std::remove_cvref_t<T>>::type; } &&
124 (std::tuple_size_v<std::remove_cvref_t<T>> >= 2) &&
125 (!std::is_base_of_v<
126 std::pair<std::tuple_element_t<0, std::remove_cvref_t<T>>,
127 std::tuple_element_t<1, std::remove_cvref_t<T>>>,
128 std::remove_cvref_t<T>>);
129
130// Forward declarations for recursive type conversions
131#ifndef DOXYGEN
132template <json_auto_list_like T> auto to_json(const T &value) -> JSON;
133template <json_auto_map_like T> auto to_json(const T &value) -> JSON;
134template <typename L, typename R>
135auto to_json(const std::pair<L, R> &value) -> JSON;
136template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON;
137template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON;
138template <typename T>
140auto to_json(const T &value) -> JSON;
141#endif
142
145template <typename T>
147auto to_json(const T &value) -> JSON {
148 return value.to_json();
149}
150
153template <typename T>
154 requires(json_auto_has_method_from<T>)
155auto from_json(const JSON &value) -> std::optional<T> {
156 return T::from_json(value);
157}
158
160template <typename T>
161 requires std::is_same_v<T, bool>
162auto from_json(const JSON &value) -> std::optional<T> {
163 if (value.is_boolean()) {
164 return value.to_boolean();
165 }
166 return std::nullopt;
167}
168
170template <typename T>
171 requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
172auto from_json(const JSON &value) -> std::optional<T> {
173 // A value outside the target type's range yields no result rather than a
174 // silently narrowed one
175 if (value.is_integer() && std::in_range<T>(value.to_integer())) {
176 return static_cast<T>(value.to_integer());
177 }
178 return std::nullopt;
179}
180
182template <typename T>
183 requires std::is_same_v<T, Decimal>
184auto from_json(const JSON &value) -> std::optional<T> {
185 if (value.is_decimal()) {
186 return value.to_decimal();
187 }
188 return std::nullopt;
189}
190
191// TODO: How can we keep this in the hash header that does not yet know about
192// JSON?
194template <typename T>
195 requires std::is_same_v<T, JSON::Object::hash_type>
196auto to_json(const T &hash) -> JSON {
197 auto result{JSON::make_array()};
198 result.push_back(JSON{static_cast<std::size_t>(hash.a >> 64)});
199 result.push_back(JSON{static_cast<std::size_t>(hash.a)});
200 result.push_back(JSON{static_cast<std::size_t>(hash.b >> 64)});
201 result.push_back(JSON{static_cast<std::size_t>(hash.b)});
202 return result;
203}
204
205// TODO: How can we keep this in the hash header that does not yet know about
206// JSON?
208template <typename T>
209 requires std::is_same_v<T, JSON::Object::hash_type>
210auto from_json(const JSON &value) -> std::optional<T> {
211 if (!value.is_array() || value.size() != 4 || !value.at(0).is_integer() ||
212 !value.at(1).is_integer() || !value.at(2).is_integer() ||
213 !value.at(3).is_integer()) {
214 return std::nullopt;
215 }
216
217 using uint128_type = JSON::Object::hash_type::type;
218 return T{(static_cast<uint128_type>(
219 static_cast<std::uint64_t>(value.at(0).to_integer()))
220 << 64) |
221 static_cast<uint128_type>(
222 static_cast<std::uint64_t>(value.at(1).to_integer())),
223 (static_cast<uint128_type>(
224 static_cast<std::uint64_t>(value.at(2).to_integer()))
225 << 64) |
226 static_cast<uint128_type>(
227 static_cast<std::uint64_t>(value.at(3).to_integer()))};
228}
229
231template <typename T>
232 requires(std::constructible_from<JSON, T> &&
233 // Otherwise MSVC gets confused
234 !std::is_same_v<T, unsigned long long>)
235auto to_json(const T &value) -> JSON {
236 return JSON{value};
237}
238
241template <typename T>
242 requires std::is_same_v<T, std::filesystem::file_time_type>
243auto to_json(const T value) -> JSON {
244 return JSON{static_cast<std::int64_t>(value.time_since_epoch().count())};
245}
246
248template <typename T>
249 requires std::is_same_v<T, std::filesystem::file_time_type>
250auto from_json(const JSON &value) -> std::optional<T> {
251 if (value.is_integer()) {
252 using file_time_type = std::filesystem::file_time_type;
253 return file_time_type{file_time_type::duration{
254 static_cast<file_time_type::duration::rep>(value.to_integer())}};
255 }
256 return std::nullopt;
257}
258
260template <typename T>
261 requires(std::is_same_v<T, std::filesystem::path> &&
262 // In at least Clang and GCC, paths are convertible to strings,
263 // resulting in ambiguous templated calls
264 !std::is_convertible_v<T, std::string>)
265auto to_json(const T value) -> JSON {
266 return JSON{value.string()};
267}
268
270template <typename T>
271 requires std::is_same_v<T, std::filesystem::path>
272auto from_json(const JSON &value) -> std::optional<T> {
273 if (value.is_string()) {
274 return value.to_string();
275 }
276 return std::nullopt;
277}
278
280template <typename T>
282auto to_json(const T &bitset) -> JSON {
283 constexpr std::size_t BIT_COUNT{json_auto_bitset_size<T>::value};
284 if constexpr (BIT_COUNT <= 64) {
285 return JSON{static_cast<std::int64_t>(bitset.to_ullong())};
286 } else {
287 return JSON{bitset.to_string()};
288 }
289}
290
292template <typename T>
294auto from_json(const JSON &value) -> std::optional<T> {
295 constexpr std::size_t BIT_COUNT{json_auto_bitset_size<T>::value};
296 if constexpr (BIT_COUNT <= 64) {
297 if (value.is_integer()) {
298 return T{static_cast<unsigned long long>(value.to_integer())};
299 }
300 return std::nullopt;
301
302 } else {
303 if (value.is_string()) {
304 return T{value.to_string()};
305 }
306 return std::nullopt;
307 }
308}
309
311template <typename T>
312 requires std::is_same_v<T, unsigned long long>
313auto to_json(const T value) -> JSON {
314 return JSON{static_cast<std::int64_t>(value)};
315}
316
318template <typename T>
319 requires std::is_same_v<T, JSON>
320auto from_json(const JSON &value) -> std::optional<T> {
321 return value;
322}
323
325template <typename T>
327auto from_json(const JSON &value) -> std::optional<T> {
328 if (value.is_string()) {
329 return value.to_string();
330 }
331 return std::nullopt;
332}
333
335template <typename T>
336 requires std::is_enum_v<T>
337auto to_json(const T value) -> JSON {
338 return to_json(static_cast<std::underlying_type_t<T>>(value));
339}
340
342template <typename T>
343 requires std::is_enum_v<T>
344auto from_json(const JSON &value) -> std::optional<T> {
345 if (value.is_integer()) {
346 return static_cast<T>(value.to_integer());
347 }
348 return std::nullopt;
349}
350
353template <typename T> auto to_json(const std::optional<T> &value) -> JSON {
354 return value.has_value() ? to_json(value.value()) : JSON{nullptr};
355}
356
358template <typename T>
359 requires requires { typename T::value_type; } &&
360 std::is_same_v<T, std::optional<typename T::value_type>>
361auto from_json(const JSON &value) -> std::optional<T> {
362 if (value.is_null()) {
363 return std::optional<T>{
364 std::optional<typename T::value_type>{std::nullopt}};
365 }
366 auto result{from_json<typename T::value_type>(value)};
367 if (!result.has_value()) {
368 return std::nullopt;
369 }
370
371 return result;
372}
373
376template <json_auto_list_like T>
377auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
378 -> JSON {
379 // TODO: Extend `make_array` to optionally take iterators, etc
380 auto result{JSON::make_array()};
381 for (auto iterator = begin; iterator != end; ++iterator) {
382 result.push_back(to_json(*iterator));
383 }
384
385 // To guarantee ordering across implementations
386 if constexpr (!json_auto_has_reverse_iterator<T>) {
387 std::sort(result.as_array().begin(), result.as_array().end());
388 }
389
390 return result;
391}
392
396template <json_auto_list_like T,
397 std::invocable<const typename T::value_type &> F>
398auto to_json(typename T::const_iterator begin, typename T::const_iterator end,
399 const F &callback) -> JSON {
400 // TODO: Extend `make_array` to optionally take iterators, etc
401 auto result{JSON::make_array()};
402 for (auto iterator = begin; iterator != end; ++iterator) {
403 result.push_back(callback(*iterator));
404 }
405
406 // To guarantee ordering across implementations
407 if constexpr (!json_auto_has_reverse_iterator<T>) {
408 std::sort(result.as_array().begin(), result.as_array().end());
409 }
410
411 return result;
412}
413
415template <json_auto_list_like T> auto to_json(const T &value) -> JSON {
416 return to_json<T>(value.cbegin(), value.cend());
417}
418
421template <json_auto_list_like T,
422 std::invocable<const typename T::value_type &> F>
423auto to_json(const T &value, const F &callback) -> JSON {
424 return to_json<T>(value.cbegin(), value.cend(), callback);
425}
426
428template <json_auto_list_like T>
429auto from_json(const JSON &value) -> std::optional<T> {
430 if (!value.is_array()) {
431 return std::nullopt;
432 }
433
434 T result;
435
436 if constexpr (requires { result.reserve(value.size()); }) {
437 result.reserve(value.size());
438 }
439
440 for (const auto &item : value.as_array()) {
441 auto subvalue{from_json<typename T::value_type>(item)};
442 if (!subvalue.has_value()) {
443 return std::nullopt;
444 }
445
446 if constexpr (requires { result.insert(subvalue.value()); }) {
447 result.insert(std::move(subvalue).value());
448 } else {
449 result.push_back(std::move(subvalue).value());
450 }
451 }
452
453 return result;
454}
455
458template <json_auto_list_like T>
460 const JSON &value,
461 const std::function<std::optional<typename T::value_type>(const JSON &)>
462 &callback) -> std::optional<T> {
463 if (!value.is_array()) {
464 return std::nullopt;
465 }
466
467 T result;
468
469 if constexpr (requires { result.reserve(value.size()); }) {
470 result.reserve(value.size());
471 }
472
473 for (const auto &item : value.as_array()) {
474 auto subvalue{callback(item)};
475 if (!subvalue.has_value()) {
476 return std::nullopt;
477 }
478
479 if constexpr (requires { result.insert(subvalue.value()); }) {
480 result.insert(std::move(subvalue).value());
481 } else {
482 result.push_back(std::move(subvalue).value());
483 }
484 }
485
486 return result;
487}
488
490template <json_auto_map_like T>
491auto to_json(typename T::const_iterator begin, typename T::const_iterator end)
492 -> JSON {
493 auto result{JSON::make_object()};
494 for (auto iterator = begin; iterator != end; ++iterator) {
495 result.assign(iterator->first, to_json(iterator->second));
496 }
497
498 return result;
499}
500
502template <json_auto_map_like T> auto to_json(const T &value) -> JSON {
503 return to_json<T>(value.cbegin(), value.cend());
504}
505
507template <json_auto_map_like T,
508 std::invocable<const typename T::mapped_type &> F>
509auto to_json(typename T::const_iterator begin, typename T::const_iterator end,
510 const F &callback) -> JSON {
511 auto result{JSON::make_object()};
512 for (auto iterator = begin; iterator != end; ++iterator) {
513 result.assign(iterator->first, callback(iterator->second));
514 }
515
516 return result;
517}
518
520template <json_auto_map_like T>
521auto from_json(const JSON &value) -> std::optional<T> {
522 if (!value.is_object()) {
523 return std::nullopt;
524 }
525
526 T result;
527 for (const auto &item : value.as_object()) {
528 auto subvalue{from_json<typename T::mapped_type>(item.second)};
529 if (!subvalue.has_value()) {
530 return std::nullopt;
531 }
532
533 result.emplace(item.first, std::move(subvalue).value());
534 }
535
536 return result;
537}
538
541template <json_auto_map_like T>
543 const JSON &value,
544 const std::function<std::optional<typename T::mapped_type>(const JSON &)>
545 &callback) -> std::optional<T> {
546 if (!value.is_object()) {
547 return std::nullopt;
548 }
549
550 T result;
551 for (const auto &item : value.as_object()) {
552 auto subvalue{callback(item.second)};
553 if (!subvalue.has_value()) {
554 return std::nullopt;
555 }
556
557 result.emplace(item.first, std::move(subvalue).value());
558 }
559
560 return result;
561}
562
564template <json_auto_map_like T,
565 std::invocable<const typename T::mapped_type &> F>
566auto to_json(const T &value, const F &callback) -> JSON {
567 return to_json<T>(value.cbegin(), value.cend(), callback);
568}
569
572template <typename L, typename R>
573auto to_json(const std::pair<L, R> &value) -> JSON {
574 auto tuple{JSON::make_array()};
575 tuple.push_back(to_json(value.first));
576 tuple.push_back(to_json(value.second));
577 return tuple;
578}
579
581template <typename T>
582 requires JSONAutoIsPair<T>::value
583auto from_json(const JSON &value) -> std::optional<T> {
584 if (!value.is_array() || value.size() != 2) {
585 return std::nullopt;
586 }
587
588 auto first{from_json<typename T::first_type>(value.at(0))};
589 auto second{from_json<typename T::second_type>(value.at(1))};
590 if (!first.has_value() || !second.has_value()) {
591 return std::nullopt;
592 }
593
594 return std::make_pair<typename T::first_type, typename T::second_type>(
595 std::move(first).value(), std::move(second).value());
596}
597
598// Handle 1-element tuples
600template <json_auto_tuple_mono T> auto to_json(const T &value) -> JSON {
601 auto tuple = JSON::make_array();
602 std::apply(
603 [&](const auto &element) -> auto { tuple.push_back(to_json(element)); },
604 value);
605 return tuple;
606}
607
609template <json_auto_tuple_mono T>
610auto from_json(const JSON &value) -> std::optional<T> {
611 if (!value.is_array() || value.size() != 1) {
612 return std::nullopt;
613 }
614
615 auto first{from_json<std::tuple_element_t<0, T>>(value.at(0))};
616 if (!first.has_value()) {
617 return std::nullopt;
618 }
619
620 return {std::move(first).value()};
621}
622
624template <json_auto_tuple_poly T> auto to_json(const T &value) -> JSON {
625 auto tuple = JSON::make_array();
626 std::apply(
627 [&tuple](const auto &...elements) -> auto {
628 (tuple.push_back(to_json(elements)), ...);
629 },
630 value);
631 return tuple;
632}
633
634#ifndef DOXYGEN
635template <typename T, std::size_t... Indices>
636auto from_json_tuple_poly(
637 const JSON &value, [[maybe_unused]] std::index_sequence<Indices...> indices)
638 -> T {
639 return {from_json<std::tuple_element_t<Indices, T>>(value.at(Indices))
640 .value()...};
641}
642#endif
643
645template <json_auto_tuple_poly T>
646auto from_json(const JSON &value) -> std::optional<T> {
647 if (!value.is_array() || value.size() != std::tuple_size_v<T>) {
648 return std::nullopt;
649 }
650
651 try {
652 return from_json_tuple_poly<T>(
653 value, std::make_index_sequence<std::tuple_size_v<T>>{});
654 // TODO: Maybe there is a better way to catch this without using exceptions?
655 } catch (const std::bad_optional_access &) {
656 return std::nullopt;
657 }
658}
659
661template <typename T>
663auto to_json(const T &value) -> JSON {
664 auto result{JSON::make_array()};
665 result.push_back(JSON{static_cast<std::int64_t>(value.index())});
666 std::visit(
667 [&result](const auto &alternative) -> auto {
668 result.push_back(to_json(alternative));
669 },
670 value);
671 return result;
672}
673
674#ifndef DOXYGEN
675template <typename T, std::size_t Index = 0>
676auto from_json_variant_impl(const JSON &data, std::size_t index)
677 -> std::optional<T> {
678 if constexpr (Index >= std::variant_size_v<T>) {
679 return std::nullopt;
680 } else {
681 if (Index == index) {
683 if (result.has_value()) {
684 return T{std::in_place_index<Index>, std::move(result).value()};
685 }
686
687 return std::nullopt;
688 }
689 return from_json_variant_impl<T, Index + 1>(data, index);
690 }
691}
692#endif
693
695template <typename T>
697auto from_json(const JSON &value) -> std::optional<T> {
698 if (!value.is_array() || value.size() != 2 || !value.at(0).is_integer()) {
699 return std::nullopt;
700 }
701
702 const auto index{static_cast<std::size_t>(value.at(0).to_integer())};
703 if (index >= std::variant_size_v<T>) {
704 return std::nullopt;
705 }
706 return from_json_variant_impl<T>(value.at(1), index);
707}
708
709} // namespace sourcemeta::core
710
711#endif
Definition json_auto.h:70
Definition json_auto.h:82
Definition json_auto.h:114
Definition json_auto.h:122
SOURCEMETA_FORCEINLINE auto as_array() const noexcept -> const Array &
Definition json_value.h:737
SOURCEMETA_FORCEINLINE auto to_boolean() const noexcept -> bool
Definition json_value.h:619
SOURCEMETA_FORCEINLINE auto as_object() noexcept -> Object &
Definition json_value.h:782
static auto make_array() -> JSON
SOURCEMETA_FORCEINLINE auto is_object() const noexcept -> bool
Definition json_value.h:571
SOURCEMETA_FORCEINLINE auto at(const Array::size_type index) const -> const JSON &
Definition json_value.h:893
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:557
static auto size(const String &value) noexcept -> std::size_t
Definition json_value.h:39
auto to_json(const T &value) -> JSON
Definition json_auto.h:147
auto from_json(const JSON &value) -> std::optional< T >
Definition json_auto.h:155
Definition json_auto.h:38
Definition json_auto.h:99
Definition json_auto.h:106
@ Index
A map of index labels carrying no RDF ranging over an object.
Definition jsonld_materialize.h:116