diff --git a/src/json.hpp b/src/json.hpp index 05fb8c922..3d4994e3f 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -106,6 +106,14 @@ 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; @@ -113,8 +121,8 @@ struct json_traits; @brief unnamed namespace with internal helper functions @since version 1.0.0 */ -// TODO transform this anon ns to detail? -namespace + +namespace detail { /*! @brief Helper to determine whether there's a key_type for T. @@ -140,6 +148,7 @@ struct has_mapped_type }; // taken from http://stackoverflow.com/questions/10711952/how-to-detect-existence-of-a-class-using-sfinae +// used to determine if json_traits is defined for a given type T template struct has_destructor { @@ -158,7 +167,23 @@ struct has_json_traits static constexpr bool value = has_destructor>::value; }; -template <> struct has_json_traits : std::false_type {}; +struct to_json_fn +{ + template + constexpr auto operator()(T&& val) const -> decltype(to_json(std::forward(val))) + { + return to_json(std::forward(val)); + } +}; + +struct from_json_fn +{ + template + constexpr auto operator()(Json const& from, T& to) const -> decltype(from_json(from, to)) + { + return from_json(from, to); + } +}; /*! @brief helper class to create locales with decimal point @@ -181,6 +206,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 @@ -1337,10 +1379,24 @@ class basic_json assert_invariant(); } + // constructor chosen if json_traits is specialized for type T + // 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 expressivety 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 < typename T, typename = - typename std::enable_if::type>::type>::value>::type> explicit basic_json(T &&val) : basic_json(json_traits::from_json(*this); + // TODO add alias templates (enable_if_t etc) template < typename T, - typename = - typename std::enable_if::type>::type>::value>::type> auto get_impl(T *) const -> decltype( json_traits::type>::type>::from_json(*this); } + // this one is quite atrocious + // this overload is chosen ONLY if json_traits struct is not specialized, and if the expression nlohmann::from_json(*this, T&) is valid + // I chose to prefer the json_traits specialization if it exists, since it's a more advanced use. + // But we can of course change this behaviour + template + auto get_impl(T *) const -> typename std::enable_if< + not detail::has_json_traits::type>::value, + typename std::remove_cv(), + std::declval()), + std::declval())>::type>::type>::type + { + typename std::remove_cv::type>::type + ret; + ::nlohmann::from_json(*this, ret); + return ret; + } + /// get an object (explicit) template ::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* /*unused*/) const { if (is_array()) @@ -2791,7 +2868,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* /*unused*/) const { if (is_array()) diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index d119625fa..cfe638673 100644 --- a/test/src/unit-constructor3.cpp +++ b/test/src/unit-constructor3.cpp @@ -42,22 +42,12 @@ struct pod_type { short c; }; -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); -} - struct bit_more_complex_type { pod_type a; pod_type b; std::string 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); -} - // best optional implementation ever template class optional_type @@ -68,11 +58,97 @@ public: 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 to_json(*opt); +} + +json to_json(no_json_traits_type const& p) +{ + json ret; + ret["a"] = p.a; + return ret; +} + +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(); +} + +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) { @@ -82,6 +158,11 @@ 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 @@ -163,7 +244,7 @@ TEST_CASE("constructors for user-defined types", "[udt]") { SECTION("empty type") { - udt::empty_type const e; + udt::empty_type const e{}; auto const j = json{e}; auto k = json::object(); CHECK(j == k); @@ -300,3 +381,118 @@ TEST_CASE("get<> for user-defined types", "[udt]") } } } + +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; + json j; + j["a"] = 42; + nlohmann::from_json(j, res); + CHECK(res == expected); + + res = j.get(); + CHECK(res == expected); + } +}