diff --git a/src/json.hpp b/src/json.hpp index a302bb02b..0248e81c3 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -91,13 +91,37 @@ 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; + +template +using remove_cv_t = typename std::remove_cv::type; + +template +using remove_reference_t = typename std::remove_reference::type; + +template +using uncvref_t = remove_cv_t>; + +// TODO update this doc /*! @brief unnamed namespace with internal helper functions @since version 1.0.0 */ -namespace + +namespace detail { /*! @brief Helper to determine whether there's a key_type for T. @@ -122,6 +146,72 @@ struct has_mapped_type std::is_integral()))>::value; }; +void to_json(); +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)))) + -> 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)) + { + 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); + } +}; + /*! @brief helper class to create locales with decimal point @@ -144,6 +234,23 @@ struct DecimalSeparator : std::numpunct } +// taken from ranges-v3 +// TODO add doc +template +struct _static_const +{ + static constexpr T value{}; +}; + +template +constexpr T _static_const::value; + +inline namespace +{ + constexpr auto const& to_json = _static_const::value; + constexpr auto const& from_json = _static_const::value; +} + /*! @brief a class to store JSON values @@ -1225,6 +1332,31 @@ 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 >()))> + explicit basic_json(T &&val) + : basic_json(::nlohmann::to_json(std::forward(val))) {} + /*! @brief create a string (explicit) @@ -2201,7 +2333,7 @@ class basic_json { std::stringstream ss; // fix locale problems - const static std::locale loc(std::locale(), new DecimalSeparator); + const static std::locale loc(std::locale(), new detail::DecimalSeparator); ss.imbue(loc); // 6, 15 or 16 digits of precision allows round-trip IEEE 754 @@ -2584,7 +2716,6 @@ class basic_json // value access // ////////////////// - /// get an object (explicit) template::value and std::is_convertible::value, int>::type = 0> @@ -2619,7 +2750,7 @@ class basic_json not std::is_same::value and not std::is_arithmetic::value and not std::is_convertible::value and - not has_mapped_type::value, int>::type = 0> + not detail::has_mapped_type::value, int>::type = 0> T get_impl(T*) const { if (is_array()) @@ -2664,7 +2795,7 @@ class basic_json /// get an array (explicit) template::value and - not has_mapped_type::value, int>::type = 0> + not detail::has_mapped_type::value, int>::type = 0> T get_impl(T*) const { if (is_array()) @@ -2898,11 +3029,24 @@ class basic_json */ template::value, int>::type = 0> - ValueType get() const + auto get() const -> decltype(this->get_impl(static_cast(nullptr))) { return get_impl(static_cast(nullptr)); } + template + auto get() const -> remove_reference_t< + decltype(::nlohmann::from_json(*this, std::declval()), + 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); + return ret; + } + /*! @brief get a pointer value (explicit) @@ -5829,7 +5973,7 @@ class basic_json o.width(0); // fix locale problems - const auto old_locale = o.imbue(std::locale(std::locale(), new DecimalSeparator)); + const auto old_locale = o.imbue(std::locale(std::locale(), new detail::DecimalSeparator)); // set precision // 6, 15 or 16 digits of precision allows round-trip IEEE 754 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 782d5b53f..b4b85a92a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(${JSON_UNITTEST_TARGET_NAME} "src/unit-concepts.cpp" "src/unit-constructor1.cpp" "src/unit-constructor2.cpp" + "src/unit-constructor3.cpp" "src/unit-convenience.cpp" "src/unit-conversions.cpp" "src/unit-deserialization.cpp" diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp new file mode 100644 index 000000000..de52762f6 --- /dev/null +++ b/test/src/unit-constructor3.cpp @@ -0,0 +1,550 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ (test suite) +| | |__ | | | | | | version 2.0.5 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2016 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include "catch.hpp" + +#include "json.hpp" +using nlohmann::json; + +namespace udt +{ +// only used by counter_type +auto nb_free_function_calls = 0; + +struct empty_type {}; +struct pod_type { + int a; + char b; + short c; +}; + +struct bit_more_complex_type { + pod_type a; + pod_type b; + std::string c; +}; + +struct counter_type +{ +}; + +// best optional implementation ever +template +class optional_type +{ +public: + optional_type() = default; + explicit optional_type(T val) : _val(std::make_shared(std::move(val))) {} + explicit operator bool() const noexcept { return _val != nullptr; } + + T const &operator*() const { return *_val; } + optional_type& operator=(T const& t) + { + _val = std::make_shared(t); + return *this; + } + +private: + std::shared_ptr _val; +}; + +struct no_json_traits_type +{ + int a; +}; + +// free to/from_json functions + +json to_json(empty_type) +{ + return json::object(); +} + +json to_json(pod_type const& p) +{ + return {{"a", p.a}, {"b", p.b}, {"c", p.c}}; +} + +json to_json(bit_more_complex_type const& p) +{ + using nlohmann::to_json; + return json{{"a", to_json(p.a)}, {"b", to_json(p.b)}, {"c", p.c}}; +} + +template +json to_json(optional_type const& opt) +{ + using nlohmann::to_json; + if (!opt) + return nullptr; + 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()); + t = empty_type{}; +} + +void from_json(json const&j, pod_type& t) +{ + t = {j["a"].get(), j["b"].get(), j["c"].get()}; +} + +void from_json(json const&j, bit_more_complex_type& t) +{ + // relying on json_traits struct here.. + t = {j["a"].get(), j["b"].get(), + 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) +{ + if (j.is_null()) + t = optional_type{}; + else + 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); +} + +inline bool operator==(bit_more_complex_type const &lhs, + bit_more_complex_type const &rhs) noexcept { + return std::tie(lhs.a, lhs.b, lhs.c) == std::tie(rhs.a, rhs.b, rhs.c); +} + +template +inline bool operator==(optional_type const& lhs, optional_type const& rhs) +{ + if (!lhs && !rhs) + return true; + if (!lhs || !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]") +{ + SECTION("empty type") + { + udt::empty_type const e{}; + auto const j = json{e}; + auto k = json::object(); + CHECK(j == k); + } + + SECTION("pod type") + { + auto const e = udt::pod_type{42, 42, 42}; + auto j = json{e}; + auto k = json{{"a", 42}, {"b", 42}, {"c", 42}}; + CHECK(j == k); + } + + SECTION("bit more complex type") + { + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + + auto j = json{e}; + auto k = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + CHECK(j == k); + } + + SECTION("vector of udt") + { + std::vector v; + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + + v.emplace_back(e); + v.emplace_back(e); + v.emplace_back(e); + + json j = v; + auto k = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + auto l = json{k, k, k}; + CHECK(j == l); + } + + SECTION("optional type") { + SECTION("regular case") { + udt::optional_type u{3}; + CHECK(json{u} == json(3)); + } + + SECTION("nullopt case") { + udt::optional_type v; + CHECK(json{v} == json{}); + } + + SECTION("optional of json convertible type") + { + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + udt::optional_type o{e}; + auto k = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + CHECK(json{o} == k); + } + + SECTION("optional of vector of json convertible type") + { + std::vector v; + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + v.emplace_back(e); + v.emplace_back(e); + v.emplace_back(e); + udt::optional_type> o{v}; + auto k = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + auto l = json{k, k, k}; + CHECK(json{o} == l); + } + } +} + +TEST_CASE("get<> for user-defined types", "[udt]") +{ + SECTION("pod type") + { + auto const e = udt::pod_type{42, 42, 42}; + auto const j = json{{"a", 42}, {"b", 42}, {"c", 42}}; + + auto const obj = j.get(); + CHECK(e == obj); + } + + SECTION("bit more complex type") + { + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + auto const j = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + + auto const obj = j.get(); + CHECK(e == obj); + } + + SECTION("vector of udt") + { + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + std::vector v{e, e, e}; + auto const j = json(v); + + auto const obj = j.get(); + CHECK(v == obj); + } + + SECTION("optional") + { + SECTION("from null") + { + udt::optional_type o; + json j; + CHECK(j.get() == o); + } + + SECTION("from value") + { + json j{{"a", 42}, {"b", 42}, {"c", 42}}; + auto v = j.get>(); + auto expected = udt::pod_type{42,42,42}; + REQUIRE(v); + 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]") +{ + SECTION("pod_type") + { + auto const e = udt::pod_type{42, 42, 42}; + auto const expected = json{{"a", 42}, {"b", 42}, {"c", 42}}; + + auto const j = nlohmann::to_json(e); + CHECK(j == expected); + } + + SECTION("bit_more_complex_type") + { + auto const e = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + auto const expected = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + auto const j = nlohmann::to_json(e); + CHECK(j == expected); + } + + SECTION("optional_type") + { + SECTION("from null") + { + udt::optional_type o; + + json expected; + auto const j = nlohmann::to_json(o); + CHECK(expected == j); + } + + SECTION("from value") + { + udt::optional_type o{{42, 42, 42}}; + + auto const expected = json{{"a", 42}, {"b", 42}, {"c", 42}}; + auto const j = nlohmann::to_json(o); + 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]") +{ + SECTION("pod_type") + { + auto const expected = udt::pod_type{42, 42, 42}; + auto const j = json{{"a", 42}, {"b", 42}, {"c", 42}}; + + udt::pod_type p; + nlohmann::from_json(j, p); + CHECK(p == expected); + } + + SECTION("bit_more_complex_type") + { + auto const expected = + udt::bit_more_complex_type{{42, 42, 42}, {41, 41, 41}, "forty"}; + auto const j = json{{"a", {{"a", 42}, {"b", 42}, {"c", 42}}}, + {"b", {{"a", 41}, {"b", 41}, {"c", 41}}}, + {"c", "forty"}}; + udt::bit_more_complex_type p; + nlohmann::from_json(j, p); + CHECK(p == expected); + } + + SECTION("optional_type") + { + SECTION("from null") + { + udt::optional_type expected; + json j; + udt::optional_type o; + + nlohmann::from_json(j, o); + CHECK(expected == o); + } + + SECTION("from value") + { + udt::optional_type expected{{42, 42, 42}}; + auto const j = json{{"a", 42}, {"b", 42}, {"c", 42}}; + udt::optional_type o; + + nlohmann::from_json(j, o); + 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); + } +}