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