diff --git a/src/json.hpp b/src/json.hpp index 8b281d93..8dc9b383 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -106,17 +106,6 @@ SOFTWARE. */ namespace nlohmann { -// TODO add real documentation before PR - -// Traits structure declaration, users can specialize it for their own types -// -// constructing a json object from a user-defined type will call the -// 'json to_json(T)' function -// -// whereas calling json::get will call 'T from_json(json const&)' -template -struct json_traits; - // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; @@ -166,64 +155,24 @@ void from_json(); struct to_json_fn { - private: - // fallback overload template - static constexpr auto - impl(T &&val, long) noexcept(noexcept(to_json(std::forward(val)))) + constexpr auto + operator()(T &&val) const noexcept(noexcept(to_json(std::forward(val)))) -> decltype(to_json(std::forward(val))) { return to_json(std::forward(val)); } - // preferred overload - template - static constexpr auto impl(T &&val, int) noexcept( - noexcept(json_traits>::to_json(std::forward(val)))) - -> decltype(json_traits>::to_json(std::forward(val))) - { - return json_traits>::to_json(std::forward(val)); - } - - public: - template - constexpr auto operator()(T &&val) const - noexcept(noexcept(to_json_fn::impl(std::forward(val), 0))) - -> decltype(to_json_fn::impl(std::forward(val), 0)) - { - // decltype(0) -> int, so the compiler will try to take the 'preferred overload' - // if there is no specialization, the 'fallback overload' will be taken by converting 0 to long - return to_json_fn::impl(std::forward(val), 0); - } }; struct from_json_fn { - private: template - static constexpr auto impl(Json const &j, T &val, - long) noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val)) + constexpr auto operator()(Json &&j, T &val) const + noexcept(noexcept(from_json(std::forward(j), val))) + -> decltype(from_json(std::forward(j), val)) { - return from_json(j, val); - } - - template - static constexpr auto - impl(Json const &j, T &val, - int) noexcept(noexcept(json_traits::from_json(j, val))) - -> decltype(json_traits::from_json(j, val)) - { - return json_traits::from_json(j, val); - } - - public: - template - constexpr auto operator()(Json const &j, T &val) const - noexcept(noexcept(from_json_fn::impl(j, val, 0))) - -> decltype(from_json_fn::impl(j, val, 0)) - { - return from_json_fn::impl(j, val, 0); + return from_json(std::forward(j), val); } }; @@ -265,6 +214,32 @@ inline namespace constexpr auto const& from_json = _static_const::value; } +// default JSONSerializer template argument +// will use ADL for serialization +struct adl_serializer +{ + template >::value>> + static auto from_json(Json&& j) -> uncvref_t(j), std::declval()), std::declval())> + { + uncvref_t ret; + ::nlohmann::from_json(std::forward(j), ret); + return ret; + } + + template + static auto from_json(Json&& j, T& val) -> decltype(::nlohmann::from_json(std::forward(j), val)) + { + ::nlohmann::from_json(std::forward(j), val); + } + + template + static auto to_json(T&& val) -> decltype(::nlohmann::to_json(std::forward(val))) + { + return ::nlohmann::to_json(std::forward(val)); + } +}; + + /*! @brief a class to store JSON values @@ -352,7 +327,8 @@ template < class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, - template class AllocatorType = std::allocator + template class AllocatorType = std::allocator, + class JSONSerializer = adl_serializer > class basic_json { @@ -1421,30 +1397,10 @@ class basic_json assert_invariant(); } - // constructor chosen for user-defined types that either have: - // - a to_json free function in their type's namespace - // - a json_traits specialization for their type - // - // If there is both a free function and a specialization, the latter will be chosen, - // since it is a more advanced use - // - // note: constructor is marked explicit to avoid the following issue: - // - // struct not_equality_comparable{}; - // - // not_equality_comparable{} == not_equality_comparable{}; - // - // this will construct implicitely 2 json objects and call operator== on them - // which can cause nasty bugs on the user's in json-unrelated code - // - // the trade-off is expressiveness in initializer-lists - // auto j = json{{"a", json(not_equality_comparable{})}}; - // - // we can remove this constraint though, since lots of ctor are not explicit already - template >()))> + // constructor chosen when JSONSerializer::to_json exists for type T + template >()))> explicit basic_json(T &&val) - : basic_json(::nlohmann::to_json(std::forward(val))) {} + : basic_json(JSONSerializer::to_json(std::forward(val))) {} /*! @brief create a string (explicit) @@ -3115,16 +3071,11 @@ class basic_json return get_impl(static_cast(nullptr)); } - template - auto get() const -> remove_reference_t< - decltype(::nlohmann::from_json(*this, std::declval()), - std::declval())> + template >::value, float>> + auto get() const -> remove_reference_t()), std::declval())> { - static_assert(std::is_default_constructible::value, - "ValueType must be default-constructible when user-defined " - "from_json method is used"); - ValueType ret; - ::nlohmann::from_json(*this, ret); + uncvref_t ret; + JSONSerializer::from_json(*this, ret); return ret; } diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index de52762f..77b31c6a 100644 --- a/test/src/unit-constructor3.cpp +++ b/test/src/unit-constructor3.cpp @@ -35,9 +35,6 @@ using nlohmann::json; namespace udt { -// only used by counter_type -auto nb_free_function_calls = 0; - struct empty_type {}; struct pod_type { int a; @@ -51,10 +48,6 @@ struct bit_more_complex_type { std::string c; }; -struct counter_type -{ -}; - // best optional implementation ever template class optional_type @@ -75,11 +68,6 @@ private: std::shared_ptr _val; }; -struct no_json_traits_type -{ - int a; -}; - // free to/from_json functions json to_json(empty_type) @@ -107,17 +95,6 @@ json to_json(optional_type const& opt) return json(*opt); } -json to_json(no_json_traits_type const& p) -{ - return {{"a", p.a}}; -} - -json to_json(counter_type) -{ - ++nb_free_function_calls; - return json::object(); -} - void from_json(json const&j, empty_type& t) { assert(j.empty()); @@ -136,11 +113,6 @@ void from_json(json const&j, bit_more_complex_type& t) j["c"].get()}; } -void from_json(json const& j, no_json_traits_type& t) -{ - t.a = j["a"].get(); -} - template void from_json(json const& j, optional_type& t) { @@ -150,11 +122,6 @@ void from_json(json const& j, optional_type& t) t = j.get(); } -void from_json(json const&, counter_type&) -{ - ++nb_free_function_calls; -} - inline bool operator==(pod_type const& lhs, pod_type const& rhs) noexcept { return std::tie(lhs.a, lhs.b, lhs.c) == std::tie(rhs.a, rhs.b, rhs.c); @@ -174,106 +141,7 @@ inline bool operator==(optional_type const& lhs, optional_type const& rhs) return false; return *lhs == *rhs; } - -inline bool operator==(no_json_traits_type const& lhs, no_json_traits_type const& rhs) -{ - return lhs.a == rhs.a; } -} - -namespace nlohmann -{ -template <> -struct json_traits -{ - using type = udt::empty_type; - - static json to_json(type) - { - return json::object(); - } - - static type from_json(json const& j) - { - assert(j.is_object() and j.empty()); - return {}; - } -}; - -template <> -struct json_traits -{ - using type = udt::pod_type; - - static json to_json(type const& t) - { - return {{"a", t.a}, {"b", t.b}, {"c", t.c}}; - } - - static type from_json(json const& j) - { - assert(j.is_object()); - return {j["a"].get(), j["b"].get(), j["c"].get()}; - } -}; - -template <> -struct json_traits -{ - using type = udt::bit_more_complex_type; - - static json to_json(type const& t) - { - return json{{"a", json{t.a}}, {"b", json{t.b}}, {"c", t.c}}; - } - - static type from_json(json const& j) - { - return {j["a"].get(), j["b"].get(), - j["c"].get()}; - } -}; - -template -struct json_traits> -{ - using type = udt::optional_type; - - static json to_json(type const& t) - { - if (t) - return json(*t); - return {}; - } - - static type from_json(json const& j) - { - if (j.is_null()) - return {}; - return type{j.get()}; - } -}; - -template <> -struct json_traits -{ - using type = udt::counter_type; - static int nb_calls; - - static json to_json(type) - { - ++nb_calls; - return json::object(); - } - - static void from_json(json const&, type&) - { - ++nb_calls; - } -}; -int json_traits::nb_calls{0}; -} - TEST_CASE("constructors for user-defined types", "[udt]") { @@ -415,24 +283,6 @@ TEST_CASE("get<> for user-defined types", "[udt]") CHECK(*v == expected); } } - - SECTION("no json_traits specialization, use of ADL") - { - udt::no_json_traits_type val{42}; - auto const expected = json{{"a", 42}}; - auto const j = json(val); - CHECK(j == expected); - } - - SECTION("counter_type") - { - // check that the traits specialization is chosen - auto const j = json{udt::counter_type{}}; - CHECK(nlohmann::json_traits::nb_calls == 1); - auto const elem = j.get(); - CHECK(nlohmann::json_traits::nb_calls == 2); - CHECK(udt::nb_free_function_calls == 0); - } } TEST_CASE("to_json free function", "[udt]") @@ -477,16 +327,6 @@ TEST_CASE("to_json free function", "[udt]") CHECK(expected == j); } } - - SECTION("no json_traits specialization") - { - udt::no_json_traits_type t{42}; - - json expected; - expected["a"] = 42; - auto const j = nlohmann::to_json(t); - CHECK(j == expected); - } } TEST_CASE("from_json free function", "[udt]") @@ -535,16 +375,4 @@ TEST_CASE("from_json free function", "[udt]") CHECK(expected == o); } } - - SECTION("no json_traits specialization") - { - udt::no_json_traits_type expected{42}; - udt::no_json_traits_type res; - auto const j = json{{"a", 42}}; - nlohmann::from_json(j, res); - CHECK(res == expected); - - res = j.get(); - CHECK(res == expected); - } }