diff --git a/docs/mkdocs/docs/api/basic_json_view/get.md b/docs/mkdocs/docs/api/basic_json_view/get.md index 40fd1033a..cd0a98bff 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get.md +++ b/docs/mkdocs/docs/api/basic_json_view/get.md @@ -19,16 +19,19 @@ For the types below, the conversion works directly on the flat index -- no `Basi document's own buffer of decoded strings (see [`get_string()`](get_string.md)) - `BasicJsonType` -- equivalent to [`materialize()`](materialize.md) - `basic_json_view` -- returns `#!cpp *this` -- `#!cpp std::vector` -- from an array, element by element, each converted with `#!cpp get()`; `#!cpp - std::vector` keeps a view of every element instead of a value -- `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is constructible from a `#!cpp - (const char*, std::size_t)` pair -- from an object, member by member, each value converted with `#!cpp get()`; with a repeated - key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and - [`materialize()`](materialize.md)) does; `#!cpp std::map` keeps views of the members - instead of values +- `#!cpp std::vector` of the types above -- from an array, element by element, each converted with + `#!cpp get()`; `#!cpp std::vector` keeps a view of every element instead of a value +- `#!cpp std::map` and `#!cpp std::unordered_map`, if `K` is + constructible from a `#!cpp (const char*, std::size_t)` pair -- views of the members of an object; with a repeated + key, the view of the *last* member is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) keeps its value -A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, member by member, without -building a `BasicJsonType` value. This covers the types of the +A default-constructible `T` whose `from_json()` accepts views reads the view directly as well, without building a +`BasicJsonType` value. This covers the library's own conversions of the standard library types -- all other +`#!cpp std::vector`s, `#!cpp std::map`/`#!cpp std::unordered_map` (also with other key types), `#!cpp std::list`, +`#!cpp std::deque`, `#!cpp std::set`, `#!cpp std::forward_list`, `#!cpp std::valarray`, `#!cpp std::array`, C arrays, +`#!cpp std::pair`, `#!cpp std::tuple`, `#!cpp std::optional`, enumerations, `#!cpp std::filesystem::path`, ... -- +which give the same values as `#!cpp materialize().get()` (from an object, a repeated key keeps the *last* value, +as [`BasicJsonType::parse()`](../basic_json/parse.md) does), as well as the types of the [`NLOHMANN_DEFINE_TYPE_*`](../macros/nlohmann_define_type_intrusive.md) and [`NLOHMANN_DEFINE_DERIVED_TYPE_*`](../macros/nlohmann_define_derived_type.md) macros and the enumerations of [`NLOHMANN_JSON_SERIALIZE_ENUM`](../macros/nlohmann_json_serialize_enum.md), and any `from_json()` constrained to @@ -44,8 +47,8 @@ T&)` of [arbitrary type conversions](../../features/arbitrary_types.md), is not keeps it: for `#!cpp {"x":1,"x":2}`, `#!cpp get()` reads `#!cpp x == 1`. See [Duplicate keys](../../features/json_view.md#duplicate-keys). -Every other `T` -- `#!cpp std::list`, `#!cpp std::pair`, `#!cpp std::array`, user types whose `from_json()` is -written for `BasicJsonType` only, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` +Every other `T` -- user types whose `from_json()` is written for `BasicJsonType` only, types without a default +constructor, `#!cpp std::tuple`s holding references, ... -- is converted by `#!cpp materialize().get()`: the subtree is built into a real `BasicJsonType` value first (as [`BasicJsonType::parse()`](../basic_json/parse.md) would), and converted from there exactly as [`BasicJsonType::get()`](../basic_json/get.md) would convert it. @@ -70,10 +73,10 @@ Strong guarantee: there are no changes to the view or the document it refers to. throws for the same JSON type and `T`. - For `#!cpp std::vector`: throws `type_error.302` if the value is not an array; otherwise, whatever converting an element to `U` throws. -- For `#!cpp std::map`/`#!cpp std::unordered_map`: throws `type_error.302` if the value is not an object; otherwise, - whatever converting a member to the mapped type throws. -- For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the macros, the same - exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the same value. +- For `#!cpp std::map`/`#!cpp std::unordered_map` of views: throws `type_error.302` if the value is not an object. +- For a `T` whose `from_json()` reads the view: whatever that `from_json()` throws; for the library's conversions and + the macros, the same exceptions with the same messages as [`BasicJsonType::get()`](../basic_json/get.md) of the + same value. - For every other `T`: whatever [`materialize().get()`](../basic_json/get.md) throws -- typically `type_error.302`, or whatever a user-provided `from_json()` throws. diff --git a/docs/mkdocs/docs/api/basic_json_view/get_to.md b/docs/mkdocs/docs/api/basic_json_view/get_to.md index 4d2dd2fa5..d11e7fd1a 100644 --- a/docs/mkdocs/docs/api/basic_json_view/get_to.md +++ b/docs/mkdocs/docs/api/basic_json_view/get_to.md @@ -9,8 +9,7 @@ Converts the value to `T` and stores it in `v`, in one of three ways: - For the types that [`get()`](get.md) converts directly -- arithmetic types, `#!cpp bool`, `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`, - `basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is - assigned: `#!cpp v = get()`. + `basic_json_view`, vectors of these, and maps of views -- `v` is assigned: `#!cpp v = get()`. - For a type whose `from_json()` accepts views (see [`get()`](get.md)), that `from_json()` is called with the view and the existing `v`, without building a `BasicJsonType` value. - For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 696afc34b..4cd1e4391 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -45,6 +45,65 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +// The from_json() overloads below read basic_json values and the views of +// (BasicJsonType is then a view, and +// json_type_of_t its basic_json type). The helpers below are +// the accesses that differ between the two. + +// the boolean of j, a boolean +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +typename BasicJsonType::boolean_t boolean_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +bool boolean_value(const View& v) +{ + return v.template get(); +} + +// the string of j, a string +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::string_t& string_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::string_t string_value(const View& v) +{ + const auto s = v.get_string(); + return typename View::string_t(s.data(), s.size()); +} + +// the binary value of j, a binary value (never a view: a view of a JSON text +// holds no binary values) +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) +{ + return {}; // LCOV_EXCL_LINE +} + +// the elements of j, an array, for a range-based for loop +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::array_t& array_elements(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +View array_elements(const View& v) noexcept +{ + return v; // (a view is two pointers) +} + template inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { @@ -73,7 +132,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // read a number value of any of the three number types into val; // returns false (leaving val untouched) if j is not a number -template +template < typename BasicJsonType, typename ArithmeticType, enable_if_t < !is_json_view::value, int > = 0 > bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) @@ -110,10 +169,22 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val) } } +// a view converts numbers itself (as basic_json does) +template::value, int> = 0> +bool get_number_value(const View& v, ArithmeticType& val) +{ + if (!v.is_number()) + { + return false; + } + val = v.template get(); + return true; +} + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, + !std::is_same::boolean_t>::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { @@ -124,31 +195,31 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { throw_type_must_be("boolean", j); } - b = *j.template get_ptr(); + b = boolean_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template < typename BasicJsonType, typename StringType, enable_if_t < - std::is_assignable::value - && is_detected_exact::value - && !std::is_same::value + std::is_assignable::string_t>::value + && is_detected_exact::string_t::value_type, value_type_t, StringType>::value + && !std::is_same::string_t, StringType>::value && !is_json_ref::value, int > = 0 > inline void from_json(const BasicJsonType& j, StringType& s) { @@ -157,23 +228,23 @@ inline void from_json(const BasicJsonType& j, StringType& s) throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_float_t& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, number_unsigned_overload_t& val) +inline void from_json(const BasicJsonType& j, number_unsigned_overload_t>& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_integer_t& val) { get_arithmetic_value(j, val); } @@ -185,8 +256,8 @@ inline void from_json(const BasicJsonType& j, EnumType& e) { using underlying_type = typename std::underlying_type::type; // get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead - using value_type = typename std::conditional::value, - typename BasicJsonType::number_unsigned_t, underlying_type>::type; + using value_type = typename std::conditional::boolean_t>::value, + typename json_type_of_t::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); e = static_cast(bool_aware_static_cast(val)); @@ -195,7 +266,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -203,16 +274,16 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l throw_type_must_be("array", j); } l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) + auto pos = l.before_begin(); + for (const auto& elem : array_elements(j)) { - return i.template get(); - }); + pos = l.insert_after(pos, elem.template get()); + } } // valarray doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -220,11 +291,11 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) throw_type_must_be("array", j); } l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) + std::size_t i = 0; + for (const auto& elem : array_elements(j)) { - return elem.template get(); - }); + l[i++] = elem.template get(); + } } // element is not itself a C array: read it directly @@ -252,7 +323,8 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines from_json_c_array_element(j, arr); } -template +// (for a view, array_t is read element by element below) +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); @@ -261,7 +333,7 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType: // fill a std::array from the JSON array j: the size is checked once and the // elements are read in order, so a short array converts the elements it has and // then throws the out_of_range error at() would throw for the first missing one -template +template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !is_json_view::value, int > = 0 > void from_json_std_array(const BasicJsonType& j, std::array& arr) { const auto& inner_array = *j.template get_ptr(); @@ -276,6 +348,21 @@ void from_json_std_array(const BasicJsonType& j, std::array& arr) } } +// (a view has no random access to the elements: they are read in order) +template::value, int> = 0> +void from_json_std_array(const View& v, std::array& arr) +{ + std::size_t size = 0; + for (auto it = v.begin(); size < N && it != v.end(); ++it) + { + arr[size++] = (*it).template get(); + } + if (JSON_HEDLEY_UNLIKELY(size < N)) + { + static_cast(v.at(size)); + } +} + template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) @@ -324,9 +411,8 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p void()) { ConstructibleArrayType ret; - const auto* inner_array = j.template get_ptr(); - reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); - for (const auto& elem : *inner_array) + reserve_if_possible(ret, j.size(), priority_tag<1> {}); + for (const auto& elem : array_elements(j)) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType @@ -337,11 +423,11 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_compatible_binary_type::value&& + is_constructible_array_type, ConstructibleArrayType>::value&& + !is_constructible_object_type, ConstructibleArrayType>::value&& + !is_constructible_string_type, ConstructibleArrayType>::value&& + !std::is_same::binary_t>::value&& + !is_compatible_binary_type, ConstructibleArrayType>::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -408,24 +494,24 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { throw_type_must_be("binary", j); } - bin = *j.template get_ptr(); + bin = binary_value(j); } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_binary_type::value, + enable_if_t < is_compatible_binary_type, CompatibleArrayType>::value, int > = 0 > inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) { if (j.is_binary()) { - bin = static_cast(*j.template get_ptr()); + bin = static_cast(binary_value(j)); } else if (j.is_array()) { @@ -454,8 +540,47 @@ void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& v obj.emplace(std::forward(key), std::forward(value)); } +// a member of a view's object: a later member with the same key replaces an +// earlier one, as parse() keeps the last value of a repeated key (containers +// without operator[] keep the first) +template +auto from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/) +-> decltype(obj[std::forward(key)] = std::forward(value), void()) +{ + obj[std::forward(key)] = std::forward(value); +} + +template +void from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/) +{ + obj.emplace(std::forward(key), std::forward(value)); +} + +template < typename BasicJsonType, typename ConstructibleObjectType, enable_if_t < !is_json_view::value, int > = 0 > +void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& ret) +{ + const auto* inner_object = j.template get_ptr(); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); + for (const auto& p : *inner_object) + { + from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); + } +} + +template::value, int> = 0> +void from_json_object_impl(const View& v, ConstructibleObjectType& ret) +{ + using string_t = typename View::basic_json_type::object_t::key_type; + reserve_if_possible(ret, v.size(), priority_tag<1> {}); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(ret, string_t(key.data(), key.size()), item.value().template get(), priority_tag<1> {}); + } +} + template::value, int> = 0> + enable_if_t, ConstructibleObjectType>::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) @@ -464,12 +589,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } ConstructibleObjectType ret; - const auto* inner_object = j.template get_ptr(); - reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); - for (const auto& p : *inner_object) - { - from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); - } + from_json_object_impl(j, ret); obj = std::move(ret); } @@ -478,10 +598,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, + !std::is_same::number_unsigned_t>::value&& + !std::is_same::number_integer_t>::value&& + !std::is_same::number_float_t>::value&& + !std::is_same::boolean_t>::value, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { @@ -495,7 +615,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { throw_type_must_be("number", j); } - val = static_cast(*j.template get_ptr()); + val = static_cast(boolean_value(j)); } template @@ -505,7 +625,7 @@ detail::uncvref_t from_json_tuple_get_impl(BasicJsonType&& j, detail::iden } template::value, int> = 0> + detail::enable_if_t>, is_compatible_reference_type>::value, int> = 0> Type from_json_tuple_get_impl(BasicJsonType && j, detail::identity_tag /*unused*/, detail::priority_tag<1> /*unused*/) { return std::forward(j).template get_ref(); @@ -549,7 +669,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(conjunction>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, conjunction, is_compatible_reference_type>>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -595,7 +715,7 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) // read a map with enum keys from an object, using the enum's own from_json for // the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written // with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS -template +template < typename BasicJsonType, typename Map, enable_if_t < !is_json_view::value, int > = 0 > inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/) { if (!j.is_object()) @@ -610,6 +730,24 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::tru return true; } +template::value, int> = 0> +inline bool from_json_enum_keyed_object(const View& v, Map& m, std::true_type /*key is enum*/) +{ + using json_t = typename View::basic_json_type; + if (!v.is_object()) + { + return false; + } + m.clear(); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(m, json_t(typename json_t::string_t(key.data(), key.size())).template get(), + item.value().template get(), priority_tag<1> {}); + } + return true; +} + template inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/) { @@ -618,7 +756,7 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -631,7 +769,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -655,7 +793,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { throw_type_must_be("string", j); } - const auto& s = *j.template get_ptr(); + const auto& s = string_value(j); // Checking for C++20 standard or later can be insufficient in case the // library support for char8_t is either incomplete or was disabled // altogether. Use the __cpp_lib_char8_t feature test instead. @@ -694,10 +832,9 @@ struct fn }; } // namespace from_json_adl -// the sources whose from_json() is only looked up by ADL: views (the library's -// overloads are written for basic_json) and the probe +// the sources whose from_json() is only looked up by ADL: the probe template -struct is_adl_only_from_json_source : disjunction, std::is_same> {}; +struct is_adl_only_from_json_source : std::is_same {}; struct from_json_fn { @@ -748,20 +885,6 @@ void from_json_base(const View& v, T& base) v.get_to(base); } -// the context of an exception thrown by a from_json() of the macros: the value -// itself for basic_json (for JSON_DIAGNOSTICS), none for a view -template::value, int> = 0> -const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept -{ - return &j; -} - -template::value, int> = 0> -std::nullptr_t from_json_context(const View& /*unused*/) noexcept -{ - return nullptr; -} - // a value as an exception message shows it (invalid UTF-8 replaced; the // strings of a view are always valid UTF-8) template::value, int> = 0> diff --git a/include/nlohmann/detail/exceptions.hpp b/include/nlohmann/detail/exceptions.hpp index d47ed6f17..ee2c0feec 100644 --- a/include/nlohmann/detail/exceptions.hpp +++ b/include/nlohmann/detail/exceptions.hpp @@ -417,6 +417,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +// the context of an exception about j: j itself for a basic_json value (whose +// path JSON_DIAGNOSTICS shows), none for a view of +template::value, int> = 0> +const BasicJsonType * exception_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t exception_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -428,7 +442,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 4f89f3463..65394b504 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -347,7 +347,7 @@ return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They diff --git a/include/nlohmann/detail/view/value.hpp b/include/nlohmann/detail/view/value.hpp index 945071705..4b54a7785 100644 --- a/include/nlohmann/detail/view/value.hpp +++ b/include/nlohmann/detail/view/value.hpp @@ -130,14 +130,16 @@ struct is_direct_value : std::integral_constant < bool, template struct is_direct_value, BasicJsonType, View> : std::true_type {}; +// (vectors of these, which are faster than through from_json(), and containers +// of views; other containers are read by the library's from_json()) template -struct is_direct_value, BasicJsonType, View> : std::true_type {}; +struct is_direct_value, BasicJsonType, View> : is_direct_value {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; } // namespace view } // namespace detail diff --git a/include/nlohmann/json_view.hpp b/include/nlohmann/json_view.hpp index 5a5f0ed66..2cb03fed8 100644 --- a/include/nlohmann/json_view.hpp +++ b/include/nlohmann/json_view.hpp @@ -910,22 +910,25 @@ class basic_json_view return *this; } - template + /// vectors of the types converted directly (faster than the library's + /// from_json()) and containers of views (no from_json() reads them: a view + /// is no value of BasicJsonType); other containers are read by from_json() + template < typename U, typename A, typename std::enable_if < detail::view::is_direct_value::value, int >::type = 0 > std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { return detail::view::vector_value(*this); } - template::value, int>::type = 0> - std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } - template::value, int>::type = 0> - std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } /// whether the from_json() of T's serializer reads views (see diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c0d8d80c9..10756f5ba 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2720,7 +2720,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP return ej_pair.second == j; \ }); \ if (it != std::end(m)) e = it->first; \ - else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::from_json_context(j))); \ + else ::nlohmann::detail::templated_json_throw(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \ } // Ugly macros to avoid uglier copy-paste when specializing basic_json. They @@ -5914,6 +5914,20 @@ void templated_json_throw(ExceptionType exception) (void)exception; } +// the context of an exception about j: j itself for a basic_json value (whose +// path JSON_DIAGNOSTICS shows), none for a view of +template::value, int> = 0> +const BasicJsonType * exception_context(const BasicJsonType& j) noexcept +{ + return &j; +} + +template::value, int> = 0> +std::nullptr_t exception_context(const View& /*unused*/) noexcept +{ + return nullptr; +} + /*! @brief throws because @a j does not have the type a conversion expects @param[in] expected the expected type(s), e.g. "array" or "binary or array" @@ -5925,7 +5939,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha { static_cast(expected); // unused when JSON_NOEXCEPTION is defined static_cast(j); - JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), &j)); + JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j))); } /*! @@ -6154,6 +6168,65 @@ NLOHMANN_JSON_NAMESPACE_BEGIN namespace detail { +// The from_json() overloads below read basic_json values and the views of +// (BasicJsonType is then a view, and +// json_type_of_t its basic_json type). The helpers below are +// the accesses that differ between the two. + +// the boolean of j, a boolean +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +typename BasicJsonType::boolean_t boolean_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +bool boolean_value(const View& v) +{ + return v.template get(); +} + +// the string of j, a string +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::string_t& string_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::string_t string_value(const View& v) +{ + const auto s = v.get_string(); + return typename View::string_t(s.data(), s.size()); +} + +// the binary value of j, a binary value (never a view: a view of a JSON text +// holds no binary values) +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) +{ + return {}; // LCOV_EXCL_LINE +} + +// the elements of j, an array, for a range-based for loop +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > +const typename BasicJsonType::array_t& array_elements(const BasicJsonType& j) +{ + return *j.template get_ptr(); +} + +template::value, int> = 0> +View array_elements(const View& v) noexcept +{ + return v; // (a view is two pointers) +} + template inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { @@ -6182,7 +6255,7 @@ void from_json(const BasicJsonType& j, std::optional& opt) // read a number value of any of the three number types into val; // returns false (leaving val untouched) if j is not a number -template +template < typename BasicJsonType, typename ArithmeticType, enable_if_t < !is_json_view::value, int > = 0 > bool get_number_value(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) @@ -6219,10 +6292,22 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val) } } +// a view converts numbers itself (as basic_json does) +template::value, int> = 0> +bool get_number_value(const View& v, ArithmeticType& val) +{ + if (!v.is_number()) + { + return false; + } + val = v.template get(); + return true; +} + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value, + !std::is_same::boolean_t>::value, int > = 0 > void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { @@ -6233,31 +6318,31 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { throw_type_must_be("boolean", j); } - b = *j.template get_ptr(); + b = boolean_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template < typename BasicJsonType, typename StringType, enable_if_t < - std::is_assignable::value - && is_detected_exact::value - && !std::is_same::value + std::is_assignable::string_t>::value + && is_detected_exact::string_t::value_type, value_type_t, StringType>::value + && !std::is_same::string_t, StringType>::value && !is_json_ref::value, int > = 0 > inline void from_json(const BasicJsonType& j, StringType& s) { @@ -6266,23 +6351,23 @@ inline void from_json(const BasicJsonType& j, StringType& s) throw_type_must_be("string", j); } - s = *j.template get_ptr(); + s = string_value(j); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_float_t& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, number_unsigned_overload_t& val) +inline void from_json(const BasicJsonType& j, number_unsigned_overload_t>& val) { get_arithmetic_value(j, val); } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::number_integer_t& val) { get_arithmetic_value(j, val); } @@ -6294,8 +6379,8 @@ inline void from_json(const BasicJsonType& j, EnumType& e) { using underlying_type = typename std::underlying_type::type; // get_arithmetic_value() does not accept boolean_t; read the number that to_json() wrote instead - using value_type = typename std::conditional::value, - typename BasicJsonType::number_unsigned_t, underlying_type>::type; + using value_type = typename std::conditional::boolean_t>::value, + typename json_type_of_t::number_unsigned_t, underlying_type>::type; value_type val; get_arithmetic_value(j, val); e = static_cast(bool_aware_static_cast(val)); @@ -6304,7 +6389,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6312,16 +6397,16 @@ inline void from_json(const BasicJsonType& j, std::forward_list& l throw_type_must_be("array", j); } l.clear(); - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) + auto pos = l.before_begin(); + for (const auto& elem : array_elements(j)) { - return i.template get(); - }); + pos = l.insert_after(pos, elem.template get()); + } } // valarray doesn't have an insert method template::value, int> = 0> + enable_if_t, T>::value, int> = 0> inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) @@ -6329,11 +6414,11 @@ inline void from_json(const BasicJsonType& j, std::valarray& l) throw_type_must_be("array", j); } l.resize(j.size()); - std::transform(j.begin(), j.end(), std::begin(l), - [](const BasicJsonType & elem) + std::size_t i = 0; + for (const auto& elem : array_elements(j)) { - return elem.template get(); - }); + l[i++] = elem.template get(); + } } // element is not itself a C array: read it directly @@ -6361,7 +6446,8 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines from_json_c_array_element(j, arr); } -template +// (for a view, array_t is read element by element below) +template < typename BasicJsonType, enable_if_t < !is_json_view::value, int > = 0 > inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); @@ -6370,7 +6456,7 @@ inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType: // fill a std::array from the JSON array j: the size is checked once and the // elements are read in order, so a short array converts the elements it has and // then throws the out_of_range error at() would throw for the first missing one -template +template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !is_json_view::value, int > = 0 > void from_json_std_array(const BasicJsonType& j, std::array& arr) { const auto& inner_array = *j.template get_ptr(); @@ -6385,6 +6471,21 @@ void from_json_std_array(const BasicJsonType& j, std::array& arr) } } +// (a view has no random access to the elements: they are read in order) +template::value, int> = 0> +void from_json_std_array(const View& v, std::array& arr) +{ + std::size_t size = 0; + for (auto it = v.begin(); size < N && it != v.end(); ++it) + { + arr[size++] = (*it).template get(); + } + if (JSON_HEDLEY_UNLIKELY(size < N)) + { + static_cast(v.at(size)); + } +} + template auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) @@ -6433,9 +6534,8 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p void()) { ConstructibleArrayType ret; - const auto* inner_array = j.template get_ptr(); - reserve_if_possible(ret, inner_array->size(), priority_tag<1> {}); - for (const auto& elem : *inner_array) + reserve_if_possible(ret, j.size(), priority_tag<1> {}); + for (const auto& elem : array_elements(j)) { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType @@ -6446,11 +6546,11 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p template < typename BasicJsonType, typename ConstructibleArrayType, enable_if_t < - is_constructible_array_type::value&& - !is_constructible_object_type::value&& - !is_constructible_string_type::value&& - !std::is_same::value&& - !is_compatible_binary_type::value&& + is_constructible_array_type, ConstructibleArrayType>::value&& + !is_constructible_object_type, ConstructibleArrayType>::value&& + !is_constructible_string_type, ConstructibleArrayType>::value&& + !std::is_same::binary_t>::value&& + !is_compatible_binary_type, ConstructibleArrayType>::value&& !is_basic_json::value, int > = 0 > auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) @@ -6517,24 +6617,24 @@ auto from_json(const BasicJsonType& j, identity_tag> tag) } template -inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename json_type_of_t::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { throw_type_must_be("binary", j); } - bin = *j.template get_ptr(); + bin = binary_value(j); } template < typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < is_compatible_binary_type::value, + enable_if_t < is_compatible_binary_type, CompatibleArrayType>::value, int > = 0 > inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin) { if (j.is_binary()) { - bin = static_cast(*j.template get_ptr()); + bin = static_cast(binary_value(j)); } else if (j.is_array()) { @@ -6563,8 +6663,47 @@ void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& v obj.emplace(std::forward(key), std::forward(value)); } +// a member of a view's object: a later member with the same key replaces an +// earlier one, as parse() keeps the last value of a repeated key (containers +// without operator[] keep the first) +template +auto from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/) +-> decltype(obj[std::forward(key)] = std::forward(value), void()) +{ + obj[std::forward(key)] = std::forward(value); +} + +template +void from_json_object_assign(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/) +{ + obj.emplace(std::forward(key), std::forward(value)); +} + +template < typename BasicJsonType, typename ConstructibleObjectType, enable_if_t < !is_json_view::value, int > = 0 > +void from_json_object_impl(const BasicJsonType& j, ConstructibleObjectType& ret) +{ + const auto* inner_object = j.template get_ptr(); + reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); + for (const auto& p : *inner_object) + { + from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); + } +} + +template::value, int> = 0> +void from_json_object_impl(const View& v, ConstructibleObjectType& ret) +{ + using string_t = typename View::basic_json_type::object_t::key_type; + reserve_if_possible(ret, v.size(), priority_tag<1> {}); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(ret, string_t(key.data(), key.size()), item.value().template get(), priority_tag<1> {}); + } +} + template::value, int> = 0> + enable_if_t, ConstructibleObjectType>::value, int> = 0> inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) @@ -6573,12 +6712,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) } ConstructibleObjectType ret; - const auto* inner_object = j.template get_ptr(); - reserve_if_possible(ret, inner_object->size(), priority_tag<1> {}); - for (const auto& p : *inner_object) - { - from_json_object_emplace(ret, p.first, p.second.template get(), priority_tag<1> {}); - } + from_json_object_impl(j, ret); obj = std::move(ret); } @@ -6587,10 +6721,10 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value&& - !std::is_same::value, + !std::is_same::number_unsigned_t>::value&& + !std::is_same::number_integer_t>::value&& + !std::is_same::number_float_t>::value&& + !std::is_same::boolean_t>::value, int > = 0 > inline void from_json(const BasicJsonType& j, ArithmeticType& val) { @@ -6604,7 +6738,7 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { throw_type_must_be("number", j); } - val = static_cast(*j.template get_ptr()); + val = static_cast(boolean_value(j)); } template @@ -6614,7 +6748,7 @@ detail::uncvref_t from_json_tuple_get_impl(BasicJsonType&& j, detail::iden } template::value, int> = 0> + detail::enable_if_t>, is_compatible_reference_type>::value, int> = 0> Type from_json_tuple_get_impl(BasicJsonType && j, detail::identity_tag /*unused*/, detail::priority_tag<1> /*unused*/) { return std::forward(j).template get_ref(); @@ -6658,7 +6792,7 @@ inline void from_json_tuple_impl(const BasicJsonType& j, std::pair& p, p template std::tuple from_json_tuple_impl(const BasicJsonType& j, identity_tag> /*unused*/, priority_tag<2> /*unused*/) { - static_assert(conjunction>, is_compatible_reference_type>...>::value, + static_assert(conjunction>, conjunction, is_compatible_reference_type>>...>::value, "Can not return a tuple containing references to types not contained in a Json, try Json::get_to()"); return from_json_tuple_impl_base<1, Args...>(j, index_sequence_for {}); } @@ -6704,7 +6838,7 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m) // read a map with enum keys from an object, using the enum's own from_json for // the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written // with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS -template +template < typename BasicJsonType, typename Map, enable_if_t < !is_json_view::value, int > = 0 > inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/) { if (!j.is_object()) @@ -6719,6 +6853,24 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::tru return true; } +template::value, int> = 0> +inline bool from_json_enum_keyed_object(const View& v, Map& m, std::true_type /*key is enum*/) +{ + using json_t = typename View::basic_json_type; + if (!v.is_object()) + { + return false; + } + m.clear(); + for (const auto item : v.items()) + { + const auto key = item.key(); + from_json_object_assign(m, json_t(typename json_t::string_t(key.data(), key.size())).template get(), + item.value().template get(), priority_tag<1> {}); + } + return true; +} + template inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/) { @@ -6727,7 +6879,7 @@ inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6740,7 +6892,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < - typename BasicJsonType::string_t, Key >::value >> + typename json_type_of_t::string_t, Key >::value >> void from_json(const BasicJsonType& j, std::unordered_map& m) { // NOLINTNEXTLINE(modernize-type-traits) we use C++11 @@ -6764,7 +6916,7 @@ inline void from_json(const BasicJsonType& j, std_fs::path& p) { throw_type_must_be("string", j); } - const auto& s = *j.template get_ptr(); + const auto& s = string_value(j); // Checking for C++20 standard or later can be insufficient in case the // library support for char8_t is either incomplete or was disabled // altogether. Use the __cpp_lib_char8_t feature test instead. @@ -6803,10 +6955,9 @@ struct fn }; } // namespace from_json_adl -// the sources whose from_json() is only looked up by ADL: views (the library's -// overloads are written for basic_json) and the probe +// the sources whose from_json() is only looked up by ADL: the probe template -struct is_adl_only_from_json_source : disjunction, std::is_same> {}; +struct is_adl_only_from_json_source : std::is_same {}; struct from_json_fn { @@ -6857,20 +7008,6 @@ void from_json_base(const View& v, T& base) v.get_to(base); } -// the context of an exception thrown by a from_json() of the macros: the value -// itself for basic_json (for JSON_DIAGNOSTICS), none for a view -template::value, int> = 0> -const BasicJsonType * from_json_context(const BasicJsonType& j) noexcept -{ - return &j; -} - -template::value, int> = 0> -std::nullptr_t from_json_context(const View& /*unused*/) noexcept -{ - return nullptr; -} - // a value as an exception message shows it (invalid UTF-8 replaced; the // strings of a view are always valid UTF-8) template::value, int> = 0> diff --git a/single_include/nlohmann/json_view.hpp b/single_include/nlohmann/json_view.hpp index 5cb16ced2..260ea7c42 100644 --- a/single_include/nlohmann/json_view.hpp +++ b/single_include/nlohmann/json_view.hpp @@ -7797,14 +7797,16 @@ struct is_direct_value : std::integral_constant < bool, template struct is_direct_value, BasicJsonType, View> : std::true_type {}; +// (vectors of these, which are faster than through from_json(), and containers +// of views; other containers are read by the library's from_json()) template -struct is_direct_value, BasicJsonType, View> : std::true_type {}; +struct is_direct_value, BasicJsonType, View> : is_direct_value {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; -template -struct is_direct_value, BasicJsonType, View> : is_string_key {}; +template +struct is_direct_value, BasicJsonType, View> : is_string_key {}; } // namespace view } // namespace detail @@ -8637,22 +8639,25 @@ class basic_json_view return *this; } - template + /// vectors of the types converted directly (faster than the library's + /// from_json()) and containers of views (no from_json() reads them: a view + /// is no value of BasicJsonType); other containers are read by from_json() + template < typename U, typename A, typename std::enable_if < detail::view::is_direct_value::value, int >::type = 0 > std::vector get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { return detail::view::vector_value(*this); } - template::value, int>::type = 0> - std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } - template::value, int>::type = 0> - std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const + template::value, int>::type = 0> + std::unordered_map get_impl(detail::view::value_tag> /*unused*/, detail::priority_tag<2> /*unused*/) const { - return detail::view::map_value>(*this); + return detail::view::map_value>(*this); } /// whether the from_json() of T's serializer reads views (see diff --git a/tests/src/unit-json_view_from_json.cpp b/tests/src/unit-json_view_from_json.cpp index f106b1fe0..ec296e5db 100644 --- a/tests/src/unit-json_view_from_json.cpp +++ b/tests/src/unit-json_view_from_json.cpp @@ -52,6 +52,11 @@ struct point int x = 0; int y = 0; NLOHMANN_DEFINE_TYPE_INTRUSIVE(point, x, y) + + friend bool operator==(const point& a, const point& b) + { + return a.x == b.x && a.y == b.y; + } }; struct shape @@ -184,6 +189,13 @@ namespace { template struct reads_view : nlohmann::detail::has_view_from_json, View, T> {}; + +// whether a member converts to the same T from the view and from the materialized value +template +bool same(const json_view& v, const json& j, const char* key) +{ + return v[key].get() == j[key].get(); +} } // namespace TEST_CASE("json_view from_json") @@ -215,39 +227,119 @@ TEST_CASE("json_view from_json") static_assert(!nlohmann::is_json_view::value, "is_json_view"); } - SECTION("the library's own conversions are not called with a view (and the probe compiles for them)") + SECTION("the library's own conversions read views") { - // these go through materialize() (vectors, maps with string keys, - // strings, and numbers are converted by the view itself) - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view>::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); - static_assert(!reads_view::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(reads_view::value, "std types"); + static_assert(!reads_view::value, "get() is materialize()"); + static_assert(reads_view::value, "std types"); #ifdef JSON_HAS_CPP_17 - static_assert(!reads_view>::value, "std types"); + static_assert(reads_view>::value, "std types"); #endif CHECK(true); } + SECTION("std types: the same values as from the materialized value") + { + const json_document d = json_document::parse(R"({"ints": [3, 1, 2, 1], "strings": ["b", "a", "b"], "pairs": [[1, "x"], [2, "y"]], + "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}], "object": {"b": 1, "a": 2}, "triple": [1, true, {"x": 5, "y": 6}], + "colors": {"green": 1, "red": 2}, "bytes": [1, 2, 255], "nested": [[1, 2], [3]], "number": 1.5, "flag": true, + "text": "hello", "nothing": null, "mixed": [1, 2.5, -3, 4], "flags": [true, false, true]})"); + const json j = d.root().materialize(); + const json_view v = d.root(); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "strings")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "mixed")); + CHECK((v["pairs"][0].get>() == j["pairs"][0].get>())); + CHECK(same>>(v, j, "pairs")); + CHECK(same>(v, j, "triple")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "pairs")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "object")); + CHECK(same>(v, j, "colors")); + CHECK(same>(v, j, "points")); + CHECK(same>(v, j, "ints")); + CHECK(same>(v, j, "flags")); + CHECK(same>(v, j, "bytes")); + CHECK(same>>(v, j, "nested")); + CHECK(same>(v, j, "mixed")); + CHECK(same(v, j, "number")); + CHECK(same(v, j, "flag")); + CHECK(same(v, j, "text")); + CHECK(same(v, j, "nothing")); + CHECK(same(v, j, "object")); + const std::valarray va = v["ints"].get>(); + CHECK(va.size() == 4); + CHECK(va[0] == 3); + int c_array[2][2] = {{0, 0}, {0, 0}}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays): the conversion to C arrays + v["nested"][0].get_to(c_array[0]); + CHECK(c_array[0][1] == 2); +#ifdef JSON_HAS_CPP_17 + CHECK(v["points"][0].get>()->y == 2); + CHECK(!v["nothing"].get>().has_value()); +#endif + // containers of views keep views of the elements + const std::vector views = v["points"].get>(); + CHECK(views.size() == 2); + CHECK(views[1]["x"].get() == 3); + CHECK(v["object"].get>().at("a").get() == 2); + } + + SECTION("std types: duplicate keys keep the last value, as from the materialized value") + { + const json_document d = json_document::parse(R"({"a": 1, "b": 2, "a": 3})"); + CHECK((d.root().get>() == d.root().materialize().get>())); + CHECK(d.root().get>().at("a") == 3); + CHECK(d.root().get>().at("a") == 3); + } + +#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS + SECTION("std types: the same errors as from the materialized value") + { + const json_document d = json_document::parse(R"([[1, "x"], {"a": 1}, "text", [1], 5, null])"); + const json j = d.root().materialize(); + for (std::size_t i = 0; i < 6; ++i) + { + CAPTURE(i) + const json_view v = d.root()[i]; + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + CHECK(exception_of_call([&] { static_cast(v.get()); }) == exception_of_call([&] { static_cast(j[i].get()); })); + CHECK(exception_of_call([&] { static_cast(v.get>()); }) == exception_of_call([&] { static_cast(j[i].get>()); })); + } + } +#endif + SECTION("macro types") { const json_document d = json_document::parse(R"({"name": "s", "points": [{"x": 1, "y": 2}, {"x": 3, "y": 4}],