From e9c856029aa09dbcaea250aae51737be281374ca Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Sat, 4 Jun 2022 00:36:09 +0200 Subject: [PATCH] Fix incorrect basic_json conversions Add wrapper types to encode conversion target value types and to_json overloads to perform the conversions. Fixes #3425. --- .../nlohmann/detail/conversions/to_json.hpp | 92 ++++++++++++++ include/nlohmann/json.hpp | 15 ++- single_include/nlohmann/json.hpp | 107 ++++++++++++++++- tests/src/unit-alt-string.cpp | 113 ++++++++++++++++-- 4 files changed, 313 insertions(+), 14 deletions(-) diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index fde46ef7c3..d7d85a6dc7 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -226,6 +226,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) { @@ -261,6 +271,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 // ///////////// @@ -285,6 +317,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 @@ -333,6 +376,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) { @@ -365,6 +420,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::to_json(*this, val.template get()); break; case value_t::string: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::string_type_wrapper + { + val.template get_ref() + }); break; case value_t::object: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::object_type_wrapper + { + val.template get_ref() + }); break; case value_t::array: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::array_type_wrapper + { + val.template get_ref() + }); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index c342172b45..9134867af8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -5112,6 +5112,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) { @@ -5147,6 +5157,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 // ///////////// @@ -5171,6 +5203,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 @@ -5219,6 +5262,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) { @@ -5251,6 +5306,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::to_json(*this, val.template get()); break; case value_t::string: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::string_type_wrapper + { + val.template get_ref() + }); break; case value_t::object: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::object_type_wrapper + { + val.template get_ref() + }); break; case value_t::array: - JSONSerializer::to_json(*this, val.template get_ref()); + JSONSerializer::to_json(*this, detail::array_type_wrapper + { + val.template get_ref() + }); break; case value_t::binary: JSONSerializer::to_json(*this, val.template get_ref()); diff --git a/tests/src/unit-alt-string.cpp b/tests/src/unit-alt-string.cpp index 26f66ecadf..6885c31d49 100644 --- a/tests/src/unit-alt-string.cpp +++ b/tests/src/unit-alt-string.cpp @@ -30,7 +30,9 @@ SOFTWARE. #include "doctest_compatibility.h" #include +using nlohmann::json; +#include #include #include @@ -328,17 +330,112 @@ 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"); + alt_json aj = j; + + 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"); + json j = aj; + + 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"}; + alt_json aj = j; + + 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"}; + json j = aj; + + 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"}}}; + alt_json aj = j; + + 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"}}}; + json j = aj; + + 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); + alt_json aj = j; + + 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); + json j = aj; + + 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())); + } + } + } }