diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index b33d726b4..826060958 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -221,6 +221,16 @@ struct external_constructor template<> struct external_constructor { + template + static void construct(BasicJsonType& j) + { + j.m_value.destroy(j.m_type); + j.m_type = value_t::object; + j.m_value.object = j.template create(); + j.set_parents(); + j.assert_invariant(); + } + template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { @@ -256,6 +266,28 @@ struct external_constructor } }; +////////////// +// wrappers // +////////////// + +template +struct array_type_wrapper +{ + const ArrayType& array; +}; + +template +struct object_type_wrapper +{ + const ObjectType& object; +}; + +template +struct string_type_wrapper +{ + const StringType& string; +}; + ///////////// // to_json // ///////////// @@ -293,6 +325,17 @@ inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) external_constructor::construct(j, std::move(s)); } +template +using string_type_constructible_from_data_and_size = decltype(typename BasicJsonType::string_t( + std::declval().data(), std::declval().size())); + +template::value, int> = 0> +void to_json(BasicJsonType& j, detail::string_type_wrapper s) +{ + external_constructor::construct(j, typename BasicJsonType::string_t(s.string.data(), s.string.size())); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, FloatType val) noexcept @@ -343,6 +386,18 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } +template +using array_type_constructible_from_iter = decltype(typename BasicJsonType::array_t( + std::declval>(), std::declval>())); + +template::value, int> = 0> +void to_json(BasicJsonType& j, detail::array_type_wrapper a) +{ + external_constructor::construct(j, + typename BasicJsonType::array_t(a.array.begin(), a.array.end())); +} + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { @@ -375,6 +430,43 @@ inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) external_constructor::construct(j, std::move(obj)); } +template < typename BasicJsonType, typename ObjectType, + enable_if_t < is_compatible_object_type::value + || is_basic_json::value, int > = 0 > +void to_json(BasicJsonType& j, const object_type_wrapper& obj) +{ + external_constructor::construct(j, obj.object); +} + +template +using object_type_key_constructible_from_data_and_size = decltype( + typename BasicJsonType::object_t::key_type( + std::declval().data(), + std::declval().size())); + +template < typename BasicJsonType, typename ObjectType, + enable_if_t < !is_compatible_object_type::value + && !is_basic_json::value + && detail::is_detected::value, int > = 0 > +void to_json(BasicJsonType& j, const object_type_wrapper& o) +{ + using std::begin; + using std::end; + + external_constructor::construct(j); + + auto& obj = j.template get_ref(); + std::transform(begin(o.object), end(o.object), std::inserter(obj, obj.end()), + [](const typename ObjectType::value_type & val) + { + return typename BasicJsonType::object_t::value_type + { + typename BasicJsonType::object_t::key_type(val.first.data(), val.first.size()), + BasicJsonType(val.second)}; + }); +} + template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !std::is_constructible + using serializer_has_to_json_with_type_wrapper = decltype(Serializer::to_json( + std::declval(), std::declval())); + + template , OtherStringType, + detail::string_type_wrapper>::value, int > = 0 > + void other_string_to_json(const OtherStringType& str) + { + JSONSerializer::to_json(*this, detail::string_type_wrapper {str}); + } + + template < typename OtherStringType, + detail::enable_if_t < + !detail::is_detected, OtherStringType, + detail::string_type_wrapper>::value, int > = 0 > + void other_string_to_json(const OtherStringType& str) + { + JSONSerializer::to_json(*this, str); + } + + template , OtherObjectType, + detail::object_type_wrapper>::value, int > = 0 > + void other_object_to_json(const OtherObjectType& obj) + { + JSONSerializer::to_json(*this, detail::object_type_wrapper {obj}); + } + + template < typename OtherObjectType, + detail::enable_if_t < + !detail::is_detected, OtherObjectType, + detail::object_type_wrapper>::value, int > = 0 > + void other_object_to_json(const OtherObjectType& obj) + { + JSONSerializer::to_json(*this, obj); + } + + template , OtherArrayType, + detail::array_type_wrapper>::value, int > = 0 > + void other_array_to_json(const OtherArrayType& arr) + { + JSONSerializer::to_json(*this, detail::array_type_wrapper {arr}); + } + + template < typename OtherArrayType, + detail::enable_if_t < + !detail::is_detected, OtherArrayType, + detail::array_type_wrapper>::value, int > = 0 > + void other_array_to_json(const OtherArrayType& arr) + { + JSONSerializer::to_json(*this, arr); + } + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > - basic_json(const BasicJsonType& val) + JSON_EXPLICIT basic_json(const BasicJsonType& val) { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -864,13 +931,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSONSerializer::to_json(*this, val.template get()); break; case value_t::string: - JSONSerializer::to_json(*this, val.template get_ref()); + other_string_to_json(val.template get_ref()); break; case value_t::object: - JSONSerializer::to_json(*this, val.template get_ref()); + other_object_to_json(val.template get_ref()); break; case value_t::array: - JSONSerializer::to_json(*this, val.template get_ref()); + other_array_to_json(val.template get_ref()); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); @@ -1665,7 +1732,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 03bdb113e..8c97775c1 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5531,6 +5531,16 @@ struct external_constructor template<> struct external_constructor { + template + static void construct(BasicJsonType& j) + { + j.m_value.destroy(j.m_type); + j.m_type = value_t::object; + j.m_value.object = j.template create(); + j.set_parents(); + j.assert_invariant(); + } + template static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) { @@ -5566,6 +5576,28 @@ struct external_constructor } }; +////////////// +// wrappers // +////////////// + +template +struct array_type_wrapper +{ + const ArrayType& array; +}; + +template +struct object_type_wrapper +{ + const ObjectType& object; +}; + +template +struct string_type_wrapper +{ + const StringType& string; +}; + ///////////// // to_json // ///////////// @@ -5603,6 +5635,17 @@ inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) external_constructor::construct(j, std::move(s)); } +template +using string_type_constructible_from_data_and_size = decltype(typename BasicJsonType::string_t( + std::declval().data(), std::declval().size())); + +template::value, int> = 0> +void to_json(BasicJsonType& j, detail::string_type_wrapper s) +{ + external_constructor::construct(j, typename BasicJsonType::string_t(s.string.data(), s.string.size())); +} + template::value, int> = 0> inline void to_json(BasicJsonType& j, FloatType val) noexcept @@ -5653,6 +5696,18 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) external_constructor::construct(j, arr); } +template +using array_type_constructible_from_iter = decltype(typename BasicJsonType::array_t( + std::declval>(), std::declval>())); + +template::value, int> = 0> +void to_json(BasicJsonType& j, detail::array_type_wrapper a) +{ + external_constructor::construct(j, + typename BasicJsonType::array_t(a.array.begin(), a.array.end())); +} + template inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { @@ -5685,6 +5740,43 @@ inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) external_constructor::construct(j, std::move(obj)); } +template < typename BasicJsonType, typename ObjectType, + enable_if_t < is_compatible_object_type::value + || is_basic_json::value, int > = 0 > +void to_json(BasicJsonType& j, const object_type_wrapper& obj) +{ + external_constructor::construct(j, obj.object); +} + +template +using object_type_key_constructible_from_data_and_size = decltype( + typename BasicJsonType::object_t::key_type( + std::declval().data(), + std::declval().size())); + +template < typename BasicJsonType, typename ObjectType, + enable_if_t < !is_compatible_object_type::value + && !is_basic_json::value + && detail::is_detected::value, int > = 0 > +void to_json(BasicJsonType& j, const object_type_wrapper& o) +{ + using std::begin; + using std::end; + + external_constructor::construct(j); + + auto& obj = j.template get_ref(); + std::transform(begin(o.object), end(o.object), std::inserter(obj, obj.end()), + [](const typename ObjectType::value_type & val) + { + return typename BasicJsonType::object_t::value_type + { + typename BasicJsonType::object_t::key_type(val.first.data(), val.first.size()), + BasicJsonType(val.second)}; + }); +} + template < typename BasicJsonType, typename T, std::size_t N, enable_if_t < !std::is_constructible + using serializer_has_to_json_with_type_wrapper = decltype(Serializer::to_json( + std::declval(), std::declval())); + + template , OtherStringType, + detail::string_type_wrapper>::value, int > = 0 > + void other_string_to_json(const OtherStringType& str) + { + JSONSerializer::to_json(*this, detail::string_type_wrapper {str}); + } + + template < typename OtherStringType, + detail::enable_if_t < + !detail::is_detected, OtherStringType, + detail::string_type_wrapper>::value, int > = 0 > + void other_string_to_json(const OtherStringType& str) + { + JSONSerializer::to_json(*this, str); + } + + template , OtherObjectType, + detail::object_type_wrapper>::value, int > = 0 > + void other_object_to_json(const OtherObjectType& obj) + { + JSONSerializer::to_json(*this, detail::object_type_wrapper {obj}); + } + + template < typename OtherObjectType, + detail::enable_if_t < + !detail::is_detected, OtherObjectType, + detail::object_type_wrapper>::value, int > = 0 > + void other_object_to_json(const OtherObjectType& obj) + { + JSONSerializer::to_json(*this, obj); + } + + template , OtherArrayType, + detail::array_type_wrapper>::value, int > = 0 > + void other_array_to_json(const OtherArrayType& arr) + { + JSONSerializer::to_json(*this, detail::array_type_wrapper {arr}); + } + + template < typename OtherArrayType, + detail::enable_if_t < + !detail::is_detected, OtherArrayType, + detail::array_type_wrapper>::value, int > = 0 > + void other_array_to_json(const OtherArrayType& arr) + { + JSONSerializer::to_json(*this, arr); + } + + public: /// @brief create a JSON value from an existing one /// @sa https://json.nlohmann.me/api/basic_json/basic_json/ template < typename BasicJsonType, detail::enable_if_t < detail::is_basic_json::value&& !std::is_same::value, int > = 0 > - basic_json(const BasicJsonType& val) + JSON_EXPLICIT basic_json(const BasicJsonType& val) { using other_boolean_t = typename BasicJsonType::boolean_t; using other_number_float_t = typename BasicJsonType::number_float_t; @@ -20080,13 +20239,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec JSONSerializer::to_json(*this, val.template get()); break; case value_t::string: - JSONSerializer::to_json(*this, val.template get_ref()); + other_string_to_json(val.template get_ref()); break; case value_t::object: - JSONSerializer::to_json(*this, val.template get_ref()); + other_object_to_json(val.template get_ref()); break; case value_t::array: - JSONSerializer::to_json(*this, val.template get_ref()); + other_array_to_json(val.template get_ref()); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); @@ -20881,7 +21040,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec int > = 0 > BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const { - return *this; + return BasicJsonType(*this); } /*! diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index 609b5d159..64622960d 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -10,7 +10,9 @@ #include "doctest_compatibility.h" #include +using nlohmann::json; +#include #include #include @@ -308,17 +310,144 @@ TEST_CASE("alternative string type") SECTION("JSON pointer") { - // conversion from json to alt_json fails to compile (see #3425); - // attempted fix(*) produces: [[['b','a','r'],['b','a','z']]] (with each char being an integer) - // (*) disable implicit conversion for json_refs of any basic_json type - // alt_json j = R"( - // { - // "foo": ["bar", "baz"] - // } - // )"_json; auto j = alt_json::parse(R"({"foo": ["bar", "baz"]})"); CHECK(j.at(alt_json::json_pointer("/foo/0")) == j["foo"][0]); CHECK(j.at(alt_json::json_pointer("/foo/1")) == j["foo"][1]); } + + SECTION("conversion (#3425)") + { + SECTION("string") + { + SECTION("json to alt_json") + { + json j("foo"); +#if JSON_USE_IMPLICIT_CONVERSIONS + alt_json aj = j; +#else + alt_json aj = alt_json(j); +#endif + + alt_string as = aj.dump(); + CHECK(j.is_string()); + CHECK(j.dump() == "\"foo\""); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + + SECTION("alt_json to json") + { + alt_json aj("foo"); +#if JSON_USE_IMPLICIT_CONVERSIONS + json j = aj; +#else + json j = json(aj); +#endif + + alt_string as = aj.dump(); + CHECK(aj.is_string()); + CHECK(j.dump() == "\"foo\""); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + } + + SECTION("array") + { + SECTION("json to alt_json") + { + json j{"foo"}; +#if JSON_USE_IMPLICIT_CONVERSIONS + alt_json aj = j; +#else + alt_json aj = alt_json(j); +#endif + + alt_string as = aj.dump(); + CHECK(j.is_array()); + CHECK(j.dump() == "[\"foo\"]"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + + SECTION("alt_json to json") + { + alt_json aj{"foo"}; +#if JSON_USE_IMPLICIT_CONVERSIONS + json j = aj; +#else + json j = json(aj); +#endif + + alt_string as = aj.dump(); + CHECK(aj.is_array()); + CHECK(j.dump() == "[\"foo\"]"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + } + + SECTION("object") + { + SECTION("json to alt_json") + { + json j{{"foo", {"bar", "baz"}}}; +#if JSON_USE_IMPLICIT_CONVERSIONS + alt_json aj = j; +#else + alt_json aj = alt_json(j); +#endif + + alt_string as = aj.dump(); + CHECK(j.is_object()); + CHECK(j.dump() == "{\"foo\":[\"bar\",\"baz\"]}"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + + SECTION("alt_json to json") + { + alt_json aj{{"foo", {"bar", "baz"}}}; +#if JSON_USE_IMPLICIT_CONVERSIONS + json j = aj; +#else + json j = json(aj); +#endif + + alt_string as = aj.dump(); + CHECK(aj.is_object()); + CHECK(j.dump() == "{\"foo\":[\"bar\",\"baz\"]}"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + } + + SECTION("binary") + { + SECTION("json to alt_json") + { + auto j = json::binary({1, 2, 3, 4}, 128); +#if JSON_USE_IMPLICIT_CONVERSIONS + alt_json aj = j; +#else + alt_json aj = alt_json(j); +#endif + + alt_string as = aj.dump(); + CHECK(j.is_binary()); + CHECK(j.dump() == "{\"bytes\":[1,2,3,4],\"subtype\":128}"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + + SECTION("alt_json to json") + { + auto aj = alt_json::binary({1, 2, 3, 4}, 128); +#if JSON_USE_IMPLICIT_CONVERSIONS + json j = aj; +#else + json j = json(aj); +#endif + + alt_string as = aj.dump(); + CHECK(aj.is_binary()); + CHECK(j.dump() == "{\"bytes\":[1,2,3,4],\"subtype\":128}"); + CHECK(j.dump() == std::string(as.data(), as.size())); + } + } + } } diff --git a/tests/src/unit-regression1.cpp b/tests/src/unit-regression1.cpp index ee7f8bc12..a0c375e71 100644 --- a/tests/src/unit-regression1.cpp +++ b/tests/src/unit-regression1.cpp @@ -1467,7 +1467,11 @@ TEST_CASE("regression tests 1") SECTION("issue #972 - Segmentation fault on G++ when trying to assign json string literal to custom json type") { +#if JSON_USE_IMPLICIT_CONVERSIONS my_json foo = R"([1, 2, 3])"_json; +#else + my_json foo = my_json(R"([1, 2, 3])"_json); +#endif } SECTION("issue #977 - Assigning between different json types") @@ -1478,7 +1482,11 @@ TEST_CASE("regression tests 1") CHECK(lj.size() == 1); CHECK(lj["x"] == 3); CHECK(ff.x == 3); +#if JSON_USE_IMPLICIT_CONVERSIONS nlohmann::json nj = lj; // This line works as expected +#else + nlohmann::json nj = nlohmann::json(lj); // This line works as expected +#endif } } diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 503d2a4f8..beae53745 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -792,12 +792,12 @@ TEST_CASE("regression tests 2") CHECK(test1.dump() == "{\"root\":{}}"); ordered_json test2; - test2[ordered_json::json_pointer(p)] = json::object(); + test2[ordered_json::json_pointer(p)] = ordered_json::object(); CHECK(test2.dump() == "{\"root\":{}}"); // json::json_pointer and ordered_json::json_pointer are the same type; behave as above ordered_json test3; - test3[json::json_pointer(p)] = json::object(); + test3[json::json_pointer(p)] = ordered_json::object(); CHECK(std::is_same::value); CHECK(test3.dump() == "{\"root\":{}}"); } @@ -877,7 +877,8 @@ TEST_CASE("regression tests 2") CHECK(td.str == "value"); } -#ifdef JSON_HAS_CPP_20 + // this is no longer supported when implicit conversions are disabled +#if defined(JSON_HAS_CPP_20) && JSON_USE_IMPLICIT_CONVERSIONS SECTION("issue #3312 - Parse to custom class from unordered_json breaks on G++11.2.0 with C++20") { // see test for #3171 diff --git a/tests/src/unit-udt.cpp b/tests/src/unit-udt.cpp index c95de4199..9103bee63 100644 --- a/tests/src/unit-udt.cpp +++ b/tests/src/unit-udt.cpp @@ -695,14 +695,22 @@ TEST_CASE("different basic_json types conversions") SECTION("null") { json j; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == nullptr); } SECTION("boolean") { json j = true; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == true); } @@ -710,49 +718,77 @@ TEST_CASE("different basic_json types conversions") { json j(json::value_t::discarded); custom_json cj; +#if JSON_USE_IMPLICIT_CONVERSIONS CHECK_NOTHROW(cj = j); +#else + CHECK_NOTHROW(cj = custom_json(j)); +#endif CHECK(cj.type() == custom_json::value_t::discarded); } SECTION("array") { json j = {1, 2, 3}; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK((cj == std::vector {1, 2, 3})); } SECTION("integer") { json j = 42; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == 42); } SECTION("float") { json j = 42.0; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == 42.0); } SECTION("unsigned") { json j = 42u; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == 42u); } SECTION("string") { json j = "forty-two"; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj == "forty-two"); } SECTION("binary") { json j = json::binary({1, 2, 3}, 42); +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif CHECK(cj.get_binary().subtype() == 42); std::vector cv = cj.get_binary(); std::vector v = j.get_binary(); @@ -762,7 +798,11 @@ TEST_CASE("different basic_json types conversions") SECTION("object") { json j = {{"forty", "two"}}; +#if JSON_USE_IMPLICIT_CONVERSIONS custom_json cj = j; +#else + custom_json cj = custom_json(j); +#endif auto m = j.get>(); CHECK(cj == m); }