diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 358b1c65..8f8162ff 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -84,13 +84,13 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) } template < - typename BasicJsonType, typename CompatibleStringType, + typename BasicJsonType, typename ConstructibleStringType, enable_if_t < - is_compatible_string_type::value and + is_constructible_string_type::value and not std::is_same::value, + ConstructibleStringType>::value, int > = 0 > -void from_json(const BasicJsonType& j, CompatibleStringType& s) +void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_UNLIKELY(not j.is_string())) { @@ -173,11 +173,11 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, } } -template -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +template +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( - arr.reserve(std::declval()), - j.template get(), + arr.reserve(std::declval()), + j.template get(), void()) { using std::end; @@ -188,12 +188,12 @@ auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, prio { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); + return i.template get(); }); } -template -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, +template +void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; @@ -204,21 +204,21 @@ void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); + return i.template get(); }); } -template ::value and - not is_compatible_object_type::value and - not is_compatible_string_type::value and - not is_basic_json::value, + is_constructible_array_type::value and + not is_constructible_object_type::value and + not is_constructible_string_type::value and + not is_basic_json::value, int > = 0 > -auto from_json(const BasicJsonType& j, CompatibleArrayType& arr) +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), -j.template get(), +j.template get(), void()) { if (JSON_UNLIKELY(not j.is_array())) @@ -230,9 +230,9 @@ void()) from_json_array_impl(j, arr, priority_tag<3> {}); } -template::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +template::value, int> = 0> +void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_UNLIKELY(not j.is_object())) { @@ -240,13 +240,13 @@ void from_json(const BasicJsonType& j, CompatibleObjectType& obj) } auto inner_object = j.template get_ptr(); - using value_type = typename CompatibleObjectType::value_type; + using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(obj, obj.begin()), [](typename BasicJsonType::object_t::value_type const & p) { - return value_type(p.first, p.second.template get()); + return value_type(p.first, p.second.template get()); }); } diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 1a1a1744..efe878f6 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -26,6 +26,15 @@ namespace detail // helpers // ///////////// +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION @@ -68,6 +77,52 @@ using from_json_function = decltype(T::from_json(std::declval()...)); template using get_template_function = decltype(std::declval().template get()); +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +template +struct has_from_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + + /////////////////// // is_ functions // /////////////////// @@ -123,6 +178,35 @@ template struct is_compatible_object_type : is_compatible_object_type_impl {}; +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t::value and + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + std::is_constructible::value and + std::is_same::value or + (has_from_json::value or + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + template struct is_compatible_string_type_impl : std::false_type {}; @@ -137,9 +221,28 @@ struct is_compatible_string_type_impl < std::is_constructible::value; }; -template +template struct is_compatible_string_type - : is_compatible_string_type_impl {}; + : is_compatible_string_type_impl {}; + +template +struct is_constructible_string_type_impl : std::false_type {}; + +template +struct is_constructible_string_type_impl < + BasicJsonType, ConstrutibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_constructible_string_type + : is_constructible_string_type_impl {}; template struct is_compatible_array_type_impl : std::false_type {}; @@ -148,18 +251,61 @@ template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, enable_if_t::value and - is_detected::value >> + is_detected::value and +// This is needed because json_reverse_iterator has a ::iterator type... +// Therefore it is detected as a CompatibleArrayType. +// The real fix would be to have an Iterable concept. + not is_iterator_traits< + std::iterator_traits>::value >> { - // This is needed because json_reverse_iterator has a ::iterator type... - // Therefore it is detected as a CompatibleArrayType. - // The real fix would be to have an Iterable concept. - static constexpr bool value = not is_iterator_traits>::value; + static constexpr bool value = + std::is_constructible::value; }; template struct is_compatible_array_type : is_compatible_array_type_impl {}; +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value and + is_detected::value and + is_detected::value and + is_complete_type< + detected_t>::value >> +{ + static constexpr bool value = + // This is needed because json_reverse_iterator has a ::iterator type, + // furthermore, std::back_insert_iterator (and other iterators) have a base class `iterator`... + // Therefore it is detected as a ConstructibleArrayType. + // The real fix would be to have an Iterable concept. + not is_iterator_traits < + std::iterator_traits>::value and + + (std::is_same::value or + has_from_json::value or + has_non_default_from_json < + BasicJsonType, typename ConstructibleArrayType::value_type >::value); +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + template struct is_compatible_integer_type_impl : std::false_type {}; @@ -187,51 +333,6 @@ struct is_compatible_integer_type : is_compatible_integer_type_impl {}; -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -template -struct has_from_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - template struct is_compatible_type_impl: std::false_type {}; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f744366c..1de78dfc 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -386,6 +386,15 @@ namespace detail // helpers // ///////////// +// Note to maintainers: +// +// Every trait in this file expects a non CV-qualified type. +// The only exceptions are in the 'aliases for detected' section +// (i.e. those of the form: decltype(T::member_function(std::declval()))) +// +// In this case, T has to be properly CV-qualified to constraint the function arguments +// (e.g. to_json(BasicJsonType&, const T&)) + template struct is_basic_json : std::false_type {}; NLOHMANN_BASIC_JSON_TPL_DECLARATION @@ -428,6 +437,52 @@ using from_json_function = decltype(T::from_json(std::declval()...)); template using get_template_function = decltype(std::declval().template get()); +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json : std::false_type {}; + +template +struct has_from_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json : std::false_type {}; + +template +struct has_non_default_from_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. +template +struct has_to_json : std::false_type {}; + +template +struct has_to_json::value>> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected_exact::value; +}; + + /////////////////// // is_ functions // /////////////////// @@ -483,6 +538,35 @@ template struct is_compatible_object_type : is_compatible_object_type_impl {}; +template +struct is_constructible_object_type_impl : std::false_type {}; + +template +struct is_constructible_object_type_impl < + BasicJsonType, ConstructibleObjectType, + enable_if_t::value and + is_detected::value >> +{ + using object_t = typename BasicJsonType::object_t; + + static constexpr bool value = + std::is_constructible::value and + std::is_same::value or + (has_from_json::value or + has_non_default_from_json < + BasicJsonType, + typename ConstructibleObjectType::mapped_type >::value); +}; + +template +struct is_constructible_object_type + : is_constructible_object_type_impl {}; + template struct is_compatible_string_type_impl : std::false_type {}; @@ -497,9 +581,28 @@ struct is_compatible_string_type_impl < std::is_constructible::value; }; -template +template struct is_compatible_string_type - : is_compatible_string_type_impl {}; + : is_compatible_string_type_impl {}; + +template +struct is_constructible_string_type_impl : std::false_type {}; + +template +struct is_constructible_string_type_impl < + BasicJsonType, ConstrutibleStringType, + enable_if_t::value >> +{ + static constexpr auto value = + std::is_constructible::value; +}; + +template +struct is_constructible_string_type + : is_constructible_string_type_impl {}; template struct is_compatible_array_type_impl : std::false_type {}; @@ -508,18 +611,61 @@ template struct is_compatible_array_type_impl < BasicJsonType, CompatibleArrayType, enable_if_t::value and - is_detected::value >> + is_detected::value and +// This is needed because json_reverse_iterator has a ::iterator type... +// Therefore it is detected as a CompatibleArrayType. +// The real fix would be to have an Iterable concept. + not is_iterator_traits< + std::iterator_traits>::value >> { - // This is needed because json_reverse_iterator has a ::iterator type... - // Therefore it is detected as a CompatibleArrayType. - // The real fix would be to have an Iterable concept. - static constexpr bool value = not is_iterator_traits>::value; + static constexpr bool value = + std::is_constructible::value; }; template struct is_compatible_array_type : is_compatible_array_type_impl {}; +template +struct is_constructible_array_type_impl : std::false_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value >> + : std::true_type {}; + +template +struct is_constructible_array_type_impl < + BasicJsonType, ConstructibleArrayType, + enable_if_t::value and + is_detected::value and + is_detected::value and + is_complete_type< + detected_t>::value >> +{ + static constexpr bool value = + // This is needed because json_reverse_iterator has a ::iterator type, + // furthermore, std::back_insert_iterator (and other iterators) have a base class `iterator`... + // Therefore it is detected as a ConstructibleArrayType. + // The real fix would be to have an Iterable concept. + not is_iterator_traits < + std::iterator_traits>::value and + + (std::is_same::value or + has_from_json::value or + has_non_default_from_json < + BasicJsonType, typename ConstructibleArrayType::value_type >::value); +}; + +template +struct is_constructible_array_type + : is_constructible_array_type_impl {}; + template struct is_compatible_integer_type_impl : std::false_type {}; @@ -547,51 +693,6 @@ struct is_compatible_integer_type : is_compatible_integer_type_impl {}; -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -template -struct has_from_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json::value>> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - template struct is_compatible_type_impl: std::false_type {}; @@ -1156,13 +1257,13 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) } template < - typename BasicJsonType, typename CompatibleStringType, + typename BasicJsonType, typename ConstructibleStringType, enable_if_t < - is_compatible_string_type::value and + is_constructible_string_type::value and not std::is_same::value, + ConstructibleStringType>::value, int > = 0 > -void from_json(const BasicJsonType& j, CompatibleStringType& s) +void from_json(const BasicJsonType& j, ConstructibleStringType& s) { if (JSON_UNLIKELY(not j.is_string())) { @@ -1245,11 +1346,11 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, } } -template -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +template +auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/) -> decltype( - arr.reserve(std::declval()), - j.template get(), + arr.reserve(std::declval()), + j.template get(), void()) { using std::end; @@ -1260,12 +1361,12 @@ auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, prio { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); + return i.template get(); }); } -template -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, +template +void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<0> /*unused*/) { using std::end; @@ -1276,21 +1377,21 @@ void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, { // get() returns *this, this won't call a from_json // method when value_type is BasicJsonType - return i.template get(); + return i.template get(); }); } -template ::value and - not is_compatible_object_type::value and - not is_compatible_string_type::value and - not is_basic_json::value, + is_constructible_array_type::value and + not is_constructible_object_type::value and + not is_constructible_string_type::value and + not is_basic_json::value, int > = 0 > -auto from_json(const BasicJsonType& j, CompatibleArrayType& arr) +auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr) -> decltype(from_json_array_impl(j, arr, priority_tag<3> {}), -j.template get(), +j.template get(), void()) { if (JSON_UNLIKELY(not j.is_array())) @@ -1302,9 +1403,9 @@ void()) from_json_array_impl(j, arr, priority_tag<3> {}); } -template::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +template::value, int> = 0> +void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_UNLIKELY(not j.is_object())) { @@ -1312,13 +1413,13 @@ void from_json(const BasicJsonType& j, CompatibleObjectType& obj) } auto inner_object = j.template get_ptr(); - using value_type = typename CompatibleObjectType::value_type; + using value_type = typename ConstructibleObjectType::value_type; std::transform( inner_object->begin(), inner_object->end(), std::inserter(obj, obj.begin()), [](typename BasicJsonType::object_t::value_type const & p) { - return value_type(p.first, p.second.template get()); + return value_type(p.first, p.second.template get()); }); } diff --git a/test/src/unit-regression.cpp b/test/src/unit-regression.cpp index 259377aa..2c21576d 100644 --- a/test/src/unit-regression.cpp +++ b/test/src/unit-regression.cpp @@ -121,6 +121,28 @@ struct nocopy j = {{"val", n.val}}; } }; + +struct Data +{ + std::string a; + std::string b; +}; + +void from_json(const json& j, Data& data) +{ + j["a"].get_to(data.a); + j["b"].get_to(data.b); +} + +bool operator==(Data const& lhs, Data const& rhs) +{ + return lhs.a == rhs.a && lhs.b == rhs.b; +} + +bool operator!=(Data const& lhs, Data const& rhs) +{ + return !(lhs == rhs); +} } ///////////////////////////////////////////////////////////////////// @@ -1665,4 +1687,24 @@ TEST_CASE("regression tests") not std::is_constructible>::value, ""); } #endif + + SECTION("issue #1299 - compile error in from_json converting to container " + "with std::pair") + { + json j = + { + {"1", {{"a", "testa_1"}, {"b", "testb_1"}}}, + {"2", {{"a", "testa_2"}, {"b", "testb_2"}}}, + {"3", {{"a", "testa_3"}, {"b", "testb_3"}}}, + }; + + std::map expected + { + {"1", {"testa_1", "testb_1" }}, + {"2", {"testa_2", "testb_2"}}, + {"3", {"testa_3", "testb_3"}}, + }; + const auto data = j.get(); + CHECK(expected == data); + } }