From c22f2d41f3986f8504727aaa4d953538eb9af3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 27 Feb 2018 11:09:53 +0100 Subject: [PATCH 1/2] missing CHECK_NOTHROW in unit-udt --- test/src/unit-udt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index f270fdc4..a6c538eb 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -730,6 +730,6 @@ TEST_CASE("Issue #924") // Prevent get>() to throw auto j = json::array(); - (void) j.get(); - (void) j.get>(); + CHECK_NOTHROW(j.get()); + CHECK_NOTHROW(j.get>()); } From 8711ec603445698f0a02eb78c053625bc48facbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 27 Feb 2018 12:11:04 +0100 Subject: [PATCH 2/2] support construction from other basic_json types Before this patch, `basic_json` types with different template arguments were treated as `CompatibleArrayType`. Which sometimes leads to recursive calls and stack overflows. This patch adds a constructor and a `get` overload to deal with different `basic_json` types. --- include/nlohmann/detail/meta.hpp | 2 +- include/nlohmann/json.hpp | 102 +++++++++++++++++++++++++++++- single_include/nlohmann/json.hpp | 104 ++++++++++++++++++++++++++++++- test/src/unit-inspection.cpp | 4 +- test/src/unit-udt.cpp | 77 +++++++++++++++++++++++ 5 files changed, 281 insertions(+), 8 deletions(-) diff --git a/include/nlohmann/detail/meta.hpp b/include/nlohmann/detail/meta.hpp index 49f1069f..b251afb6 100644 --- a/include/nlohmann/detail/meta.hpp +++ b/include/nlohmann/detail/meta.hpp @@ -233,7 +233,7 @@ struct is_compatible_complete_type { static constexpr bool value = not std::is_base_of::value and - not std::is_same::value and + not is_basic_json::value and not is_basic_json_nested_type::value and has_to_json::value; }; diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index f92729f3..7252f9ed 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1207,6 +1207,7 @@ class basic_json - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - @a CompatibleType is not a @ref basic_json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a @@ -1242,6 +1243,77 @@ class basic_json assert_invariant(); } + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.1.2 + */ + template ::value and not std::is_same::value, int> = 0> + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); + } + /*! @brief create a container (array or object) from an initializer list @@ -2414,6 +2486,31 @@ class basic_json return *this; } + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.1.2 + */ + template::value and + detail::is_basic_json::value + , + int> = 0> + BasicJsonType get() const + { + return *this; + } + + /*! @brief get a value (explicit) @@ -2455,7 +2552,7 @@ class basic_json */ template, detail::enable_if_t < - not std::is_same::value and + not detail::is_basic_json::value and detail::has_from_json::value and not detail::has_non_default_from_json::value, int> = 0> @@ -2721,7 +2818,8 @@ class basic_json template < typename ValueType, typename std::enable_if < not std::is_pointer::value and not std::is_same>::value and - not std::is_same::value + not std::is_same::value and + not detail::is_basic_json::value #ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 and not std::is_same>::value #endif diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 3dcb834b..83e413df 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -466,7 +466,7 @@ struct is_compatible_complete_type { static constexpr bool value = not std::is_base_of::value and - not std::is_same::value and + not is_basic_json::value and not is_basic_json_nested_type::value and has_to_json::value; }; @@ -10805,6 +10805,7 @@ class basic_json - @a CompatibleType is not derived from `std::istream`, - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move constructors), + - @a CompatibleType is not a different @ref basic_json type (i.e. with different template arguments) - @a CompatibleType is not a @ref basic_json nested type (e.g., @ref json_pointer, @ref iterator, etc ...) - @ref @ref json_serializer has a @@ -10840,6 +10841,77 @@ class basic_json assert_invariant(); } + /*! + @brief create a JSON value from an existing one + + This is a constructor for existing @ref basic_json types. + It does not hijack copy/move constructors, since the parameter has different template arguments than the current ones. + + The constructor tries to convert the internal @ref m_value of the parameter. + + @tparam BasicJsonType a type such that: + - @a BasicJsonType is a @ref basic_json type. + - @a BasicJsonType has different template arguments than @ref basic_json_t. + + @param[in] val the @ref basic_json value to be converted. + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @since version 3.1.2 + */ + template ::value and not std::is_same::value, int> = 0> + basic_json(const BasicJsonType& val) + { + using other_boolean_t = typename BasicJsonType::boolean_t; + using other_number_float_t = typename BasicJsonType::number_float_t; + using other_number_integer_t = typename BasicJsonType::number_integer_t; + using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using other_string_t = typename BasicJsonType::string_t; + using other_object_t = typename BasicJsonType::object_t; + using other_array_t = typename BasicJsonType::array_t; + + switch (val.type()) + { + case value_t::boolean: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_float: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_integer: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::number_unsigned: + JSONSerializer::to_json(*this, val.template get()); + break; + case value_t::string: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::object: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::array: + JSONSerializer::to_json(*this, val.template get_ref()); + break; + case value_t::null: + *this = nullptr; + break; + case value_t::discarded: + m_type = value_t::discarded; + break; + } + assert_invariant(); + } + /*! @brief create a container (array or object) from an initializer list @@ -12012,6 +12084,31 @@ class basic_json return *this; } + /*! + @brief get special-case overload + + This overloads converts the current @ref basic_json in a different @ref basic_json type + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this, converted into @tparam BasicJsonType + + @complexity Depending on the implementation of the called `from_json()` + method. + + @since version 3.1.2 + */ + template::value and + detail::is_basic_json::value + , + int> = 0> + BasicJsonType get() const + { + return *this; + } + + /*! @brief get a value (explicit) @@ -12053,7 +12150,7 @@ class basic_json */ template, detail::enable_if_t < - not std::is_same::value and + not detail::is_basic_json::value and detail::has_from_json::value and not detail::has_non_default_from_json::value, int> = 0> @@ -12319,7 +12416,8 @@ class basic_json template < typename ValueType, typename std::enable_if < not std::is_pointer::value and not std::is_same>::value and - not std::is_same::value + not std::is_same::value and + not detail::is_basic_json::value #ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 and not std::is_same>::value #endif diff --git a/test/src/unit-inspection.cpp b/test/src/unit-inspection.cpp index a486dc04..1f07dff7 100644 --- a/test/src/unit-inspection.cpp +++ b/test/src/unit-inspection.cpp @@ -316,8 +316,8 @@ TEST_CASE("object inspection") SECTION("round trips") { for (const auto& s : - {"3.141592653589793", "1000000000000000010E5" - }) + {"3.141592653589793", "1000000000000000010E5" + }) { json j1 = json::parse(s); std::string s1 = j1.dump(); diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index a6c538eb..8a5ac73f 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -693,6 +693,83 @@ TEST_CASE("custom serializer that does adl by default", "[udt]") CHECK(me == cj.get()); } +TEST_CASE("different basic_json types conversions") +{ + using json = nlohmann::json; + + SECTION("null") + { + json j; + custom_json cj = j; + CHECK(cj == nullptr); + } + + SECTION("boolean") + { + json j = true; + custom_json cj = j; + CHECK(cj == true); + } + + SECTION("discarded") + { + json j(json::value_t::discarded); + custom_json cj; + CHECK_NOTHROW(cj = j); + CHECK(cj.type() == custom_json::value_t::discarded); + } + + SECTION("array") + { + json j = {1, 2, 3}; + custom_json cj = j; + CHECK((cj == std::vector {1, 2, 3})); + } + + SECTION("integer") + { + json j = 42; + custom_json cj = j; + CHECK(cj == 42); + } + + SECTION("float") + { + json j = 42.0; + custom_json cj = j; + CHECK(cj == 42.0); + } + + SECTION("unsigned") + { + json j = 42u; + custom_json cj = j; + CHECK(cj == 42u); + } + + SECTION("string") + { + json j = "forty-two"; + custom_json cj = j; + CHECK(cj == "forty-two"); + } + + SECTION("object") + { + json j = {{"forty", "two"}}; + custom_json cj = j; + auto m = j.get>(); + CHECK(cj == m); + } + + SECTION("get") + { + json j = 42; + custom_json cj = j.get(); + CHECK(cj == 42); + } +} + namespace { struct incomplete;