From ca811a28ee7583bb5bb24cadc3110ab9a445bbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Delrieu?= Date: Sun, 16 Oct 2016 17:29:57 +0200 Subject: [PATCH 01/40] add first version support for user-defined types --- src/json.hpp | 101 ++++++++--- test/CMakeLists.txt | 1 + test/src/unit-constructor3.cpp | 300 +++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+), 19 deletions(-) create mode 100644 test/src/unit-constructor3.cpp diff --git a/src/json.hpp b/src/json.hpp index 8dfdbb85e..f3cf1bf4f 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -91,12 +91,14 @@ SOFTWARE. */ namespace nlohmann { - +template +struct json_traits; /*! @brief unnamed namespace with internal helper functions @since version 1.0.0 */ +// TODO transform this anon ns to detail? namespace { /*! @@ -122,6 +124,47 @@ struct has_mapped_type std::is_integral()))>::value; }; +// taken from http://stackoverflow.com/questions/10711952/how-to-detect-existence-of-a-class-using-sfinae +template +struct has_destructor +{ + template + static std::true_type detect(decltype(std::declval().~U())*); + + template + static std::false_type detect(...); + + static constexpr bool value = decltype(detect(0))::value; +}; + +template +struct has_json_traits +{ + static constexpr bool value = has_destructor>::value; +}; + +template <> struct has_json_traits : std::false_type {}; + +/*! +@brief helper class to create locales with decimal point + +This struct is used a default locale during the JSON serialization. JSON +requires the decimal point to be `.`, so this function overloads the +`do_decimal_point()` function to return `.`. This function is called by +float-to-string conversions to retrieve the decimal separator between integer +and fractional parts. + +@sa https://github.com/nlohmann/json/issues/51#issuecomment-86869315 +@since version 2.0.0 +*/ +struct DecimalSeparator : std::numpunct +{ + char do_decimal_point() const + { + return '.'; + } +}; + } /*! @@ -1205,6 +1248,15 @@ class basic_json assert_invariant(); } + template < + typename T, + typename = + typename std::enable_if::type>::type>::value>::type> + explicit basic_json(T &&val) + : basic_json(json_traits::type>::type>:: + to_json(std::forward(val))) {} /*! @brief create a string (explicit) @@ -1221,15 +1273,14 @@ class basic_json @sa @ref basic_json(const typename string_t::value_type*) -- create a string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string + value from a compatible string container @since version 1.0.0 */ - basic_json(const string_t& val) - : m_type(value_t::string), m_value(val) - { - assert_invariant(); + basic_json(const string_t &val) : m_type(value_t::string), m_value(val) { + assert_invariant(); } /*! @@ -2563,20 +2614,32 @@ class basic_json // value access // ////////////////// + template < + typename T, + typename = + typename std::enable_if::type>::type>::value>::type> + auto get_impl(T *) const -> decltype( + json_traits::type>::type>::from_json(std::declval())) { + return json_traits::type>::type>::from_json(*this); + } + /// get an object (explicit) - template::value and - std::is_convertible::value, int>::type = 0> - T get_impl(T*) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } - else - { - throw std::domain_error("type must be object, but is " + type_name()); - } + template ::value and + std::is_convertible::value, + int>::type = 0> + T get_impl(T *) const { + if (is_object()) { + return T(m_value.object->begin(), m_value.object->end()); + } else { + throw std::domain_error("type must be object, but is " + type_name()); + } } /// get an object (explicit) 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..357585ff1 --- /dev/null +++ b/test/src/unit-constructor3.cpp @@ -0,0 +1,300 @@ +/* + __ _____ _____ _____ + __| | __| | | | 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 +{ +struct empty_type {}; +struct pod_type { + int a; + char b; + 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 +{ +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; } + +private: + std::shared_ptr _val; +}; + +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; +} +} + +template +struct type_helper +{ + using type = T; +}; + +namespace nlohmann +{ +template <> +struct json_traits : type_helper +{ + 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 : type_helper +{ + 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 + : type_helper +{ + 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> : type_helper> { + 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()}; + } +}; +} + + +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); + } + } +} \ No newline at end of file From 8da5ad5acb4a28552bf5b5edae78442e5d80cb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Mon, 17 Oct 2016 23:30:15 +0200 Subject: [PATCH 02/40] fix compilation on gcc-6 --- test/src/unit-constructor3.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index 357585ff1..d119625fa 100644 --- a/test/src/unit-constructor3.cpp +++ b/test/src/unit-constructor3.cpp @@ -84,17 +84,13 @@ inline bool operator==(optional_type const& lhs, optional_type const& rhs) } } -template -struct type_helper -{ - using type = T; -}; - namespace nlohmann { template <> -struct json_traits : type_helper +struct json_traits { + using type = udt::empty_type; + static json to_json(type) { return json::object(); @@ -108,8 +104,10 @@ struct json_traits : type_helper }; template <> -struct json_traits : type_helper -{ +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}}; @@ -124,8 +122,9 @@ struct json_traits : type_helper template <> struct json_traits - : type_helper { + 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}}; @@ -139,8 +138,11 @@ struct json_traits }; template -struct json_traits> : type_helper> { - static json to_json(type const&t) +struct json_traits> +{ + using type = udt::optional_type; + + static json to_json(type const& t) { if (t) return json(*t); @@ -297,4 +299,4 @@ TEST_CASE("get<> for user-defined types", "[udt]") CHECK(*v == expected); } } -} \ No newline at end of file +} From 528315fb03ed8867ccf9a4946a8757d5db3a384a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Mon, 17 Oct 2016 23:41:53 +0200 Subject: [PATCH 03/40] anonymous namespace renamed to detail --- src/json.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index f3cf1bf4f..f8085da8d 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -98,8 +98,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. @@ -143,8 +143,6 @@ struct has_json_traits static constexpr bool value = has_destructor>::value; }; -template <> struct has_json_traits : std::false_type {}; - /*! @brief helper class to create locales with decimal point @@ -1251,7 +1249,7 @@ class basic_json template < typename T, typename = - typename std::enable_if::type>::type>::value>::type> explicit basic_json(T &&val) : basic_json(json_traits::type>::type>::value>::type> auto get_impl(T *) const -> decltype( json_traits::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()) @@ -2706,7 +2704,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()) From d6fea62a29070610562f61d5dbcb594b093925a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 18 Oct 2016 00:37:35 +0200 Subject: [PATCH 04/40] wip: add free function to_json/from_json support --- src/json.hpp | 34 ++++++++++++++ test/src/unit-constructor3.cpp | 84 +++++++++++++++++++++++++++++----- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index f8085da8d..7c428002c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -143,6 +143,24 @@ struct has_json_traits static constexpr bool value = has_destructor>::value; }; +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()(T&& val) const -> decltype(from_json(std::forward(val))) + { + return from_json(std::forward(val)); + } +}; + /*! @brief helper class to create locales with decimal point @@ -165,6 +183,22 @@ struct DecimalSeparator : std::numpunct } +// taken from ranges-v3 +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 diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index d119625fa..912772f3d 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 @@ -73,6 +63,43 @@ private: std::shared_ptr _val; }; +// 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); +} + +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) { @@ -163,7 +190,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 +327,38 @@ 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); + } + } +} From 2bb49e1555df95e47a8d7a5496b6fa99aea1b47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 18 Oct 2016 11:37:32 +0200 Subject: [PATCH 05/40] wip: modify from_json prototype --- src/json.hpp | 6 +-- test/src/unit-constructor3.cpp | 91 ++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 7c428002c..5c8bdaca3 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -154,10 +154,10 @@ struct to_json_fn struct from_json_fn { - template - constexpr auto operator()(T&& val) const -> decltype(from_json(std::forward(val))) + template + constexpr auto operator()(Json const& from, T& to) const -> decltype(from_json(from, to)) { - return from_json(std::forward(val)); + return from_json(from, to); } }; diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index 912772f3d..3e025c154 100644 --- a/test/src/unit-constructor3.cpp +++ b/test/src/unit-constructor3.cpp @@ -58,6 +58,11 @@ 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; @@ -81,6 +86,33 @@ json to_json(bit_more_complex_type const& p) return json{{"a", to_json(p.a)}, {"b", to_json(p.b)}, {"c", p.c}}; } +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()}; +} + +template +void from_json(json const& j, optional_type& t) +{ + if (j.is_null()) + t = optional_type{}; + else + t = j.get(); +} + template json to_json(optional_type const& opt) { @@ -360,5 +392,64 @@ TEST_CASE("to_json free function", "[udt]") 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); + } + } +} + +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}}; + + // i really dislike this output parameter + 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); + } + } } From 3bbccb1544e3198d922c10522590f27308fafe50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 18 Oct 2016 13:36:43 +0200 Subject: [PATCH 06/40] wip: add get_impl overload that uses free from_json method --- src/json.hpp | 49 ++++++++++++++++++++++++-- test/src/unit-constructor3.cpp | 63 ++++++++++++++++++++++++++++------ 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 5c8bdaca3..edaecccc5 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -91,6 +91,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; @@ -125,6 +133,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 { @@ -184,6 +193,7 @@ struct DecimalSeparator : std::numpunct } // taken from ranges-v3 +// TODO add doc template struct __static_const { @@ -1280,6 +1290,20 @@ 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 = @@ -2646,10 +2670,13 @@ class basic_json // value access // ////////////////// + // get_impl overload chosen if json_traits struct is specialized for type T + // simply returns 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 _val; }; +struct no_json_traits_type +{ + int a; +}; + // free to/from_json functions json to_json(empty_type) @@ -86,6 +91,22 @@ json to_json(bit_more_complex_type const& p) 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()); @@ -104,6 +125,11 @@ 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) { @@ -113,15 +139,6 @@ void from_json(json const& j, optional_type& t) t = j.get(); } -template -json to_json(optional_type const& opt) -{ - using nlohmann::to_json; - if (!opt) - return nullptr; - return to_json(*opt); -} - 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); @@ -141,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 @@ -402,6 +424,16 @@ 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]") @@ -411,7 +443,6 @@ TEST_CASE("from_json free function", "[udt]") auto const expected = udt::pod_type{42, 42, 42}; auto const j = json{{"a", 42}, {"b", 42}, {"c", 42}}; - // i really dislike this output parameter udt::pod_type p; nlohmann::from_json(j, p); CHECK(p == expected); @@ -450,6 +481,18 @@ TEST_CASE("from_json free function", "[udt]") 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); } } From eb58a4e51c2edb94433fabd8c882987bbd86efe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 18 Oct 2016 23:45:58 +0200 Subject: [PATCH 07/40] add alias templates to reduce boilerplate --- src/json.hpp | 60 +++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index edaecccc5..4942fa622 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -102,6 +102,17 @@ namespace nlohmann 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; + +// TODO update this doc /*! @brief unnamed namespace with internal helper functions @since version 1.0.0 @@ -1304,15 +1315,11 @@ class basic_json // 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> + template >>::value>> explicit basic_json(T &&val) - : basic_json(json_traits::type>::type>:: - to_json(std::forward(val))) {} + : basic_json(json_traits>>::to_json( + std::forward(val))) {} /*! @brief create a string (explicit) @@ -2672,17 +2679,12 @@ class basic_json // get_impl overload chosen if json_traits struct is specialized for type T // simply returns json_traits::from_json(*this); - // TODO add alias templates (enable_if_t etc) - template < - typename T, - typename = typename std::enable_if< - detail::has_json_traits::type>::type>::value>::type> - auto get_impl(T *) const -> decltype( - json_traits::type>::type>::from_json(std::declval())) { - return json_traits::type>::type>::from_json(*this); + template >>::value>> + auto get_impl(T *) const + -> decltype(json_traits>>::from_json( + std::declval())) { + return json_traits>>::from_json(*this); } // this one is quite atrocious @@ -2690,15 +2692,19 @@ class basic_json // 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 + auto get_impl(T *) const + -> enable_if_t>::value, + remove_cv_t(), + std::declval()), + std::declval())>>> { - typename std::remove_cv::type>::type - ret; + remove_cv_t ret; + // I guess this output parameter is the only way to get ADL + // Even if users can use the get method to have a more 'functional' behaviour + // i.e. having a return type, could there be a way to have the same behaviour with from_json? + // e.g. auto t = nlohmann::from_json(json{}); + // this seems to require variable templates though... (at least it did when I tried to implement it) ::nlohmann::from_json(*this, ret); return ret; } From d2f8d439f918bd6d04243b36cfb5ef8be0338acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Thu, 20 Oct 2016 13:37:50 +0200 Subject: [PATCH 08/40] revert useless formatting --- src/json.hpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 4942fa622..94e16ef18 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -1336,14 +1336,15 @@ class basic_json @sa @ref basic_json(const typename string_t::value_type*) -- create a string value from a character pointer - @sa @ref basic_json(const CompatibleStringType&) -- create a string - value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value from a compatible string container @since version 1.0.0 */ - basic_json(const string_t &val) : m_type(value_t::string), m_value(val) { - assert_invariant(); + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) + { + assert_invariant(); } /*! @@ -2709,20 +2710,19 @@ class basic_json return ret; } - /// get an object (explicit) - template ::value and - std::is_convertible::value, - int>::type = 0> - T get_impl(T *) const { - if (is_object()) { - return T(m_value.object->begin(), m_value.object->end()); - } else { - throw std::domain_error("type must be object, but is " + type_name()); - } + template::value and + std::is_convertible::value, int>::type = 0> + T get_impl(T*) const + { + if (is_object()) + { + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); + } } /// get an object (explicit) From 34c9f092a252f8e8cea9542e0eadaee70b8f8e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Thu, 20 Oct 2016 13:45:48 +0200 Subject: [PATCH 09/40] rename __static_const to _static_const (reserved identifier) --- src/json.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 94e16ef18..bfed605ff 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -206,18 +206,18 @@ struct DecimalSeparator : std::numpunct // taken from ranges-v3 // TODO add doc template -struct __static_const +struct _static_const { static constexpr T value{}; }; template -constexpr T __static_const::value; +constexpr T _static_const::value; inline namespace { - constexpr auto const& to_json = __static_const::value; - constexpr auto const& from_json = __static_const::value; + constexpr auto const& to_json = _static_const::value; + constexpr auto const& from_json = _static_const::value; } /*! From 27d4416a09bd2ccf9bf50ffb392c677b6ae2b958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Thu, 20 Oct 2016 14:02:31 +0200 Subject: [PATCH 10/40] use uncvref_t instead of remove_cv_t>> --- src/json.hpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index bfed605ff..22ac3a778 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -112,6 +112,9 @@ 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 @@ -1315,11 +1318,12 @@ class basic_json // auto j = json{{"a", json(not_equality_comparable{})}}; // // we can remove this constraint though, since lots of ctor are not explicit already - template >>::value>> + template >::value>> explicit basic_json(T &&val) - : basic_json(json_traits>>::to_json( - std::forward(val))) {} + : basic_json(json_traits>::to_json(std::forward(val))) + { + } /*! @brief create a string (explicit) @@ -2680,12 +2684,12 @@ class basic_json // get_impl overload chosen if json_traits struct is specialized for type T // simply returns json_traits::from_json(*this); - template >>::value>> - auto get_impl(T *) const - -> decltype(json_traits>>::from_json( - std::declval())) { - return json_traits>>::from_json(*this); + template >::value>> + auto get_impl(T *) const -> decltype( + json_traits>::from_json(std::declval())) + { + return json_traits>::from_json(*this); } // this one is quite atrocious @@ -2693,12 +2697,11 @@ class basic_json // 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 - -> enable_if_t>::value, - remove_cv_t(), + auto get_impl(T *) const -> enable_if_t< + not detail::has_json_traits>::value, + uncvref_t(), std::declval()), - std::declval())>>> + std::declval())>> { remove_cv_t ret; // I guess this output parameter is the only way to get ADL From 93dabed0a8af9cfa3ee05770d3a065f1a8c5ee3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Thu, 20 Oct 2016 18:02:07 +0200 Subject: [PATCH 11/40] remove has_destructor and has_json_traits, use decltype instead --- src/json.hpp | 45 ++++++++++++--------------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 22ac3a778..0fa121b0c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -146,26 +146,6 @@ struct has_mapped_type std::is_integral()))>::value; }; -// 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 -{ - template - static std::true_type detect(decltype(std::declval().~U())*); - - template - static std::false_type detect(...); - - static constexpr bool value = decltype(detect(0))::value; -}; - -template -struct has_json_traits -{ - static constexpr bool value = has_destructor>::value; -}; - struct to_json_fn { template @@ -1318,8 +1298,7 @@ class basic_json // auto j = json{{"a", json(not_equality_comparable{})}}; // // we can remove this constraint though, since lots of ctor are not explicit already - template >::value>> + template >::to_json(std::declval>()))> explicit basic_json(T &&val) : basic_json(json_traits>::to_json(std::forward(val))) { @@ -2684,24 +2663,19 @@ class basic_json // get_impl overload chosen if json_traits struct is specialized for type T // simply returns json_traits::from_json(*this); - template >::value>> - auto get_impl(T *) const -> decltype( - json_traits>::from_json(std::declval())) + // dual argument to avoid conflicting with get_impl overloads taking a pointer + template + auto get_impl(int, int) const -> decltype(json_traits>::from_json(*this)) { return json_traits>::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 -> enable_if_t< - not detail::has_json_traits>::value, - uncvref_t(), - std::declval()), - std::declval())>> + auto get_impl(long, long) const -> uncvref_t()), + std::declval())> { remove_cv_t ret; // I guess this output parameter is the only way to get ADL @@ -3026,11 +3000,16 @@ class basic_json */ template::value, int>::type = 0> - ValueType get() const + auto get() const -> decltype(get_impl(static_cast(nullptr))) { return get_impl(static_cast(nullptr)); } + template + auto get() const -> decltype(get_impl(0, 0)) + { + return get_impl(0, 0); + } /*! @brief get a pointer value (explicit) From d36ae188e1dce0186d2f5d3f3f089d215f273d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Fri, 21 Oct 2016 16:28:01 +0200 Subject: [PATCH 12/40] move most SFINAE trickery in to/from_json_fn --- src/json.hpp | 121 +++++++++++++++++++++------------ test/src/unit-constructor3.cpp | 68 +++++++++++++++--- 2 files changed, 137 insertions(+), 52 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 0fa121b0c..467e1308f 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -148,20 +148,65 @@ struct has_mapped_type struct to_json_fn { - template - constexpr auto operator()(T&& val) const -> decltype(to_json(std::forward(val))) - { - return to_json(std::forward(val)); - } + 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 { - template - constexpr auto operator()(Json const& from, T& to) const -> decltype(from_json(from, to)) - { - return from_json(from, to); - } + 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); + } }; /*! @@ -1284,7 +1329,13 @@ class basic_json assert_invariant(); } - // constructor chosen if json_traits is specialized for type T + // 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{}; @@ -1294,15 +1345,15 @@ class basic_json // 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 + // 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 >::to_json(std::declval>()))> + template >()))> explicit basic_json(T &&val) - : basic_json(json_traits>::to_json(std::forward(val))) - { - } + : basic_json(::nlohmann::to_json(std::forward(val))) {} + /*! @brief create a string (explicit) @@ -2661,32 +2712,6 @@ class basic_json // value access // ////////////////// - // get_impl overload chosen if json_traits struct is specialized for type T - // simply returns json_traits::from_json(*this); - // dual argument to avoid conflicting with get_impl overloads taking a pointer - template - auto get_impl(int, int) const -> decltype(json_traits>::from_json(*this)) - { - return json_traits>::from_json(*this); - } - - // 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(long, long) const -> uncvref_t()), - std::declval())> - { - remove_cv_t ret; - // I guess this output parameter is the only way to get ADL - // Even if users can use the get method to have a more 'functional' behaviour - // i.e. having a return type, could there be a way to have the same behaviour with from_json? - // e.g. auto t = nlohmann::from_json(json{}); - // this seems to require variable templates though... (at least it did when I tried to implement it) - ::nlohmann::from_json(*this, ret); - return ret; - } - template::value and std::is_convertible::value, int>::type = 0> @@ -3006,10 +3031,18 @@ class basic_json } template - auto get() const -> decltype(get_impl(0, 0)) + auto get() const -> remove_reference_t< + decltype(::nlohmann::from_json(*this, std::declval()), + std::declval())> { - return get_impl(0, 0); + 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) diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index cfe638673..de52762f6 100644 --- a/test/src/unit-constructor3.cpp +++ b/test/src/unit-constructor3.cpp @@ -35,6 +35,9 @@ using nlohmann::json; namespace udt { +// only used by counter_type +auto nb_free_function_calls = 0; + struct empty_type {}; struct pod_type { int a; @@ -48,6 +51,10 @@ struct bit_more_complex_type { std::string c; }; +struct counter_type +{ +}; + // best optional implementation ever template class optional_type @@ -97,14 +104,18 @@ json to_json(optional_type const& opt) using nlohmann::to_json; if (!opt) return nullptr; - return to_json(*opt); + return json(*opt); } json to_json(no_json_traits_type const& p) { - json ret; - ret["a"] = p.a; - return ret; + 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) @@ -139,6 +150,11 @@ 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); @@ -176,7 +192,7 @@ struct json_traits { return json::object(); } - + static type from_json(json const& j) { assert(j.is_object() and j.empty()); @@ -193,7 +209,7 @@ struct json_traits { return {{"a", t.a}, {"b", t.b}, {"c", t.c}}; } - + static type from_json(json const& j) { assert(j.is_object()); @@ -237,6 +253,25 @@ struct json_traits> 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}; } @@ -380,6 +415,24 @@ 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]") @@ -487,8 +540,7 @@ TEST_CASE("from_json free function", "[udt]") { udt::no_json_traits_type expected{42}; udt::no_json_traits_type res; - json j; - j["a"] = 42; + auto const j = json{{"a", 42}}; nlohmann::from_json(j, res); CHECK(res == expected); From e90553f863524083224864d73cd7d2529470441e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Delrieu?= Date: Sat, 22 Oct 2016 19:04:59 +0200 Subject: [PATCH 13/40] fix compilation on vs2015 (taken from MS-ranges) --- src/json.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/json.hpp b/src/json.hpp index 467e1308f..9ee0de1df 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -146,6 +146,9 @@ struct has_mapped_type std::is_integral()))>::value; }; +void to_json(); +void from_json(); + struct to_json_fn { private: From 0ec64b6df4569b9b68183aa8bdfcf8de03978a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Sun, 23 Oct 2016 17:56:13 +0200 Subject: [PATCH 14/40] fix compilation on gcc 4.9 --- src/json.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/json.hpp b/src/json.hpp index 9ee0de1df..4f51f449c 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -3028,7 +3028,7 @@ class basic_json */ template::value, int>::type = 0> - auto get() const -> decltype(get_impl(static_cast(nullptr))) + auto get() const -> decltype(this->get_impl(static_cast(nullptr))) { return get_impl(static_cast(nullptr)); } From 7d6dbcfbf449243fa35590f8cd20b202b4ee9400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Mon, 7 Nov 2016 23:24:54 +0100 Subject: [PATCH 15/40] add first version for alternate implementation --- src/json.hpp | 131 ++++++++----------------- test/src/unit-constructor3.cpp | 172 --------------------------------- 2 files changed, 41 insertions(+), 262 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 4f51f449c..8b912555a 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -91,17 +91,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; @@ -151,64 +140,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); } }; @@ -251,6 +200,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 @@ -337,7 +312,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 { @@ -1332,30 +1308,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) @@ -3033,16 +2989,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 de52762f6..77b31c6ac 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); - } } From d45533b32fde63fe9b80fa1006103f30071ac6cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 8 Nov 2016 13:16:14 +0100 Subject: [PATCH 16/40] add template arguments for JSONSerializer --- src/json.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 8b912555a..da432f92f 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -200,8 +200,9 @@ inline namespace constexpr auto const& from_json = _static_const::value; } -// default JSONSerializer template argument +// default JSONSerializer template argument, doesn't care about template argument // will use ADL for serialization +template struct adl_serializer { template >::value>> @@ -313,7 +314,7 @@ template < class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, template class AllocatorType = std::allocator, - class JSONSerializer = adl_serializer + template class JSONSerializer = adl_serializer > class basic_json { @@ -1309,9 +1310,9 @@ class basic_json } // constructor chosen when JSONSerializer::to_json exists for type T - template >()))> + template >::to_json(std::declval>()))> explicit basic_json(T &&val) - : basic_json(JSONSerializer::to_json(std::forward(val))) {} + : basic_json(JSONSerializer>::to_json(std::forward(val))) {} /*! @brief create a string (explicit) @@ -2990,10 +2991,10 @@ class basic_json } template >::value, float>> - auto get() const -> remove_reference_t()), std::declval())> + auto get() const -> remove_reference_t>::from_json(*this, std::declval()), std::declval())> { uncvref_t ret; - JSONSerializer::from_json(*this, ret); + JSONSerializer>::from_json(*this, ret); return ret; } From 7dbafbe4bac80ec255b893fda31fa53eac467c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 8 Nov 2016 13:17:16 +0100 Subject: [PATCH 17/40] renamed unit-constructor3.cpp to unit-udt.cpp --- test/CMakeLists.txt | 2 +- test/src/{unit-constructor3.cpp => unit-udt.cpp} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/src/{unit-constructor3.cpp => unit-udt.cpp} (100%) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b4b85a92a..e5d649cfc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,7 +14,6 @@ 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" @@ -33,6 +32,7 @@ add_executable(${JSON_UNITTEST_TARGET_NAME} "src/unit-regression.cpp" "src/unit-serialization.cpp" "src/unit-testsuites.cpp" + "src/unit-udt.cpp" "src/unit-unicode.cpp" ) diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-udt.cpp similarity index 100% rename from test/src/unit-constructor3.cpp rename to test/src/unit-udt.cpp From 75a9cf116e518300710ffbb2435b35931545c22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Wed, 9 Nov 2016 23:55:03 +0100 Subject: [PATCH 18/40] to_json and from_json takes both two arguments now the first is the basic_json type, the second the user-defined type --- src/json.hpp | 51 +++++++++++++++++++------------------------ test/src/unit-udt.cpp | 35 +++++++++++++++-------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index da432f92f..e96a5759a 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -140,24 +140,23 @@ void from_json(); struct to_json_fn { - template + template constexpr auto - operator()(T &&val) const noexcept(noexcept(to_json(std::forward(val)))) - -> decltype(to_json(std::forward(val))) + operator()(Json&& j, T &&val) const noexcept(noexcept(to_json(std::forward(j), std::forward(val)))) + -> decltype(to_json(std::forward(j), std::forward(val)), void()) { - return to_json(std::forward(val)); + to_json(std::forward(j), std::forward(val)); } - }; struct from_json_fn { - template + template constexpr auto operator()(Json &&j, T &val) const noexcept(noexcept(from_json(std::forward(j), val))) - -> decltype(from_json(std::forward(j), val)) + -> decltype(from_json(std::forward(j), val), void()) { - return from_json(std::forward(j), val); + from_json(std::forward(j), val); } }; @@ -205,28 +204,19 @@ inline namespace template 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)) + template + static auto from_json(Json&& j, T& val) -> decltype(::nlohmann::from_json(std::forward(j), val), void()) { ::nlohmann::from_json(std::forward(j), val); } - template - static auto to_json(T&& val) -> decltype(::nlohmann::to_json(std::forward(val))) + template + static auto to_json(Json& j, T&& val) -> decltype(::nlohmann::to_json(j, std::forward(val)), void()) { - return ::nlohmann::to_json(std::forward(val)); + ::nlohmann::to_json(j, std::forward(val)); } }; - /*! @brief a class to store JSON values @@ -1310,9 +1300,11 @@ class basic_json } // constructor chosen when JSONSerializer::to_json exists for type T - template >::to_json(std::declval>()))> + template >::to_json(std::declval(), std::declval>()))> explicit basic_json(T &&val) - : basic_json(JSONSerializer>::to_json(std::forward(val))) {} + { + JSONSerializer>::to_json(*this, std::forward(val)); + } /*! @brief create a string (explicit) @@ -2990,11 +2982,14 @@ class basic_json return get_impl(static_cast(nullptr)); } - template >::value, float>> - auto get() const -> remove_reference_t>::from_json(*this, std::declval()), std::declval())> + template >::from_json(std::declval(), std::declval()))> + auto get() const -> uncvref_t { - uncvref_t ret; - JSONSerializer>::from_json(*this, ret); + using type = uncvref_t; + static_assert(std::is_default_constructible::value && std::is_copy_constructible::value, + "user-defined types must be DefaultConstructible and CopyConstructible when used with get"); + type ret; + JSONSerializer::from_json(*this, ret); return ret; } diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index 77b31c6ac..17494a45c 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -70,32 +70,31 @@ private: // free to/from_json functions -json to_json(empty_type) +void to_json(json& j, empty_type) { - return json::object(); + j = json::object(); } -json to_json(pod_type const& p) +void to_json(json& j, pod_type const& p) { - return {{"a", p.a}, {"b", p.b}, {"c", p.c}}; + j = json{{"a", p.a}, {"b", p.b}, {"c", p.c}}; } -json to_json(bit_more_complex_type const& p) +void to_json(json& j, 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}}; + j = json{{"a", json(p.a)}, {"b", json(p.b)}, {"c", p.c}}; } template -json to_json(optional_type const& opt) +void to_json(json& j, optional_type const& opt) { - using nlohmann::to_json; if (!opt) - return nullptr; - return json(*opt); + j = nullptr; + else + j = json(*opt); } -void from_json(json const&j, empty_type& t) +void from_json(json const& j, empty_type& t) { assert(j.empty()); t = empty_type{}; @@ -292,7 +291,8 @@ TEST_CASE("to_json free function", "[udt]") 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); + json j; + nlohmann::to_json(j, e); CHECK(j == expected); } @@ -303,7 +303,8 @@ TEST_CASE("to_json free function", "[udt]") 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); + json j; + nlohmann::to_json(j, e); CHECK(j == expected); } @@ -314,7 +315,8 @@ TEST_CASE("to_json free function", "[udt]") udt::optional_type o; json expected; - auto const j = nlohmann::to_json(o); + json j; + nlohmann::to_json(j, o); CHECK(expected == j); } @@ -323,7 +325,8 @@ TEST_CASE("to_json free function", "[udt]") udt::optional_type o{{42, 42, 42}}; auto const expected = json{{"a", 42}, {"b", 42}, {"c", 42}}; - auto const j = nlohmann::to_json(o); + json j; + nlohmann::to_json(j, o); CHECK(expected == j); } } From 88f7eb63d9dd6eabf42b0fd71e7f0dac711a4aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Thu, 10 Nov 2016 11:28:29 +0100 Subject: [PATCH 19/40] fix compilation: add a return in constexpr functions --- src/json.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index e96a5759a..2d3efda4e 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -145,7 +145,7 @@ struct to_json_fn operator()(Json&& j, T &&val) const noexcept(noexcept(to_json(std::forward(j), std::forward(val)))) -> decltype(to_json(std::forward(j), std::forward(val)), void()) { - to_json(std::forward(j), std::forward(val)); + return to_json(std::forward(j), std::forward(val)); } }; @@ -156,7 +156,7 @@ struct from_json_fn noexcept(noexcept(from_json(std::forward(j), val))) -> decltype(from_json(std::forward(j), val), void()) { - from_json(std::forward(j), val); + return from_json(std::forward(j), val); } }; From a5d81ff3c2a7d5a43444e3a79f78abe411ea0eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Tue, 15 Nov 2016 14:22:12 +0100 Subject: [PATCH 20/40] add basic test for custom serializer --- src/json.hpp | 62 +++++++++++++++++++++++++++++++++++-------- test/src/unit-udt.cpp | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 2d3efda4e..88c7e9c9f 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -135,6 +135,38 @@ struct has_mapped_type std::is_integral()))>::value; }; +template