From ce0b3fe5a334567825a554a08b76c725d0790500 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sat, 21 Jan 2017 16:12:17 +0100 Subject: [PATCH 01/88] :construction: made type_name() public --- doc/examples/type_name.cpp | 24 ++++++++++++++++++++++++ doc/examples/type_name.link | 1 + doc/examples/type_name.output | 7 +++++++ src/json.hpp | 5 ++++- src/json.hpp.re2c | 5 ++++- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 doc/examples/type_name.cpp create mode 100644 doc/examples/type_name.link create mode 100644 doc/examples/type_name.output diff --git a/doc/examples/type_name.cpp b/doc/examples/type_name.cpp new file mode 100644 index 0000000000..815e92d7e0 --- /dev/null +++ b/doc/examples/type_name.cpp @@ -0,0 +1,24 @@ +#include + +using json = nlohmann::json; + +int main() +{ + // create JSON values + json j_null; + json j_boolean = true; + json j_number_integer = 17; + json j_number_float = 23.42; + json j_object = {{"one", 1}, {"two", 2}}; + json j_array = {1, 2, 4, 8, 16}; + json j_string = "Hello, world"; + + // call type_name() + std::cout << j_null.type_name() << '\n'; + std::cout << j_boolean.type_name() << '\n'; + std::cout << j_number_integer.type_name() << '\n'; + std::cout << j_number_float.type_name() << '\n'; + std::cout << j_object.type_name() << '\n'; + std::cout << j_array.type_name() << '\n'; + std::cout << j_string.type_name() << '\n'; +} diff --git a/doc/examples/type_name.link b/doc/examples/type_name.link new file mode 100644 index 0000000000..39d1f973ee --- /dev/null +++ b/doc/examples/type_name.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/type_name.output b/doc/examples/type_name.output new file mode 100644 index 0000000000..ad906a4977 --- /dev/null +++ b/doc/examples/type_name.output @@ -0,0 +1,7 @@ +null +boolean +number +number +object +array +string diff --git a/src/json.hpp b/src/json.hpp index fa9b85f263..2c1fd65858 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7767,7 +7767,6 @@ class basic_json /// @} - private: /////////////////////////// // convenience functions // /////////////////////////// @@ -7782,6 +7781,9 @@ class basic_json @complexity Constant. + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,typename} + @since version 1.0.0 */ std::string type_name() const @@ -7805,6 +7807,7 @@ class basic_json } } + private: /*! @brief calculates the extra space to escape a JSON string diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 886b9dde5d..f552a390da 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -7767,7 +7767,6 @@ class basic_json /// @} - private: /////////////////////////// // convenience functions // /////////////////////////// @@ -7782,6 +7781,9 @@ class basic_json @complexity Constant. + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,typename} + @since version 1.0.0 */ std::string type_name() const @@ -7805,6 +7807,7 @@ class basic_json } } + private: /*! @brief calculates the extra space to escape a JSON string From b443edf49e2741bd27cdfb148fa42abf58d6bac7 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 02/88] add first version support for user-defined types --- src/json.hpp | 96 +++++++++-- test/CMakeLists.txt | 1 + test/src/unit-constructor3.cpp | 302 +++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+), 16 deletions(-) create mode 100644 test/src/unit-constructor3.cpp diff --git a/src/json.hpp b/src/json.hpp index 2c1fd65858..05fb8c9229 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -106,12 +106,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 { /*! @@ -137,7 +139,47 @@ struct has_mapped_type std::is_integral()))>::value; }; -} // namespace +// 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 '.'; + } +}; + /*! @brief a class to store JSON values @@ -1295,6 +1337,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) @@ -1311,15 +1362,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(); } /*! @@ -2655,16 +2705,30 @@ 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* /*unused*/) const - { - if (is_object()) - { - return T(m_value.object->begin(), m_value.object->end()); - } + 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 { JSON_THROW(std::domain_error("type must be object, but is " + type_name())); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 62213ad316..8279b157e5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,6 +15,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 0000000000..d119625fa6 --- /dev/null +++ b/test/src/unit-constructor3.cpp @@ -0,0 +1,302 @@ +/* + __ _____ _____ _____ + __| | __| | | | 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; +} +} + +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()}; + } +}; +} + + +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); + } + } +} From fe628b585b5bccbea2687dbb7f3557717086ef6e 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/88] anonymous namespace renamed to detail --- src/json.hpp | 93 ++++++++++++-- test/src/unit-constructor3.cpp | 218 +++++++++++++++++++++++++++++++-- 2 files changed, 292 insertions(+), 19 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 05fb8c9229..3d4994e3fa 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 d119625fa6..cfe6386730 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 @@ class optional_type 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); + } +} From d54d6bb84cc9d0cd74155dc0e10b15a4023f3b7f 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 04/88] add alias templates to reduce boilerplate --- src/json.hpp | 94 ++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 3d4994e3fa..23709788a9 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -117,6 +117,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 @@ -1393,15 +1404,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) @@ -1418,14 +1425,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(); } /*! @@ -2763,17 +2771,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 @@ -2781,31 +2784,34 @@ 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 - { - typename std::remove_cv::type>::type - ret; + auto get_impl(T *) const + -> enable_if_t>::value, + remove_cv_t(), + std::declval()), + 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; } - /// 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 { + 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 + { JSON_THROW(std::domain_error("type must be object, but is " + type_name())); } From 877d96c1d80ad860a6d5c22766cd8682e5cd889d 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 05/88] 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 23709788a9..68ac29fcc8 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -220,18 +220,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 12b4555b13678902d39ee9e2c4d18822e9b4455e 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 06/88] 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 68ac29fcc8..528c92b24d 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -127,6 +127,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 @@ -1404,11 +1407,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) @@ -2771,12 +2775,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 @@ -2784,12 +2788,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 03b391c37bb234c23c2c993057ff4be64efc7883 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 07/88] 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 528c92b24d..390d4e7926 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -161,26 +161,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 @@ -1407,8 +1387,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))) { @@ -2775,24 +2754,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 @@ -3108,11 +3082,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 4cdc61e49356533c67227d74fffe39588388b0ee 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 08/88] move most SFINAE trickery in to/from_json_fn --- src/json.hpp | 128 +++++++++++++++++++++------------ test/src/unit-constructor3.cpp | 68 +++++++++++++++--- 2 files changed, 142 insertions(+), 54 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 390d4e7926..8b281d938a 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -161,22 +161,70 @@ struct has_mapped_type std::is_integral()))>::value; }; +void to_json(); +void from_json(); + 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); + } }; /*! @@ -1373,7 +1421,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{}; @@ -1383,15 +1437,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) @@ -2752,32 +2806,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> @@ -3082,16 +3110,24 @@ 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)); } template - auto get() const -> decltype(get_impl(0, 0)) - { - return get_impl(0, 0); + 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) diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index cfe6386730..de52762f67 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 7dc268ef420124c741171538b362b954f4b00296 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 09/88] 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 8b281d938a..8dc9b383de 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -106,17 +106,6 @@ SOFTWARE. */ namespace nlohmann { -// TODO add real documentation before PR - -// Traits structure declaration, users can specialize it for their own types -// -// constructing a json object from a user-defined type will call the -// 'json to_json(T)' function -// -// whereas calling json::get will call 'T from_json(json const&)' -template -struct json_traits; - // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; @@ -166,64 +155,24 @@ void from_json(); struct to_json_fn { - private: - // fallback overload template - static constexpr auto - impl(T &&val, long) noexcept(noexcept(to_json(std::forward(val)))) + constexpr auto + operator()(T &&val) const noexcept(noexcept(to_json(std::forward(val)))) -> decltype(to_json(std::forward(val))) { return to_json(std::forward(val)); } - // preferred overload - template - static constexpr auto impl(T &&val, int) noexcept( - noexcept(json_traits>::to_json(std::forward(val)))) - -> decltype(json_traits>::to_json(std::forward(val))) - { - return json_traits>::to_json(std::forward(val)); - } - - public: - template - constexpr auto operator()(T &&val) const - noexcept(noexcept(to_json_fn::impl(std::forward(val), 0))) - -> decltype(to_json_fn::impl(std::forward(val), 0)) - { - // decltype(0) -> int, so the compiler will try to take the 'preferred overload' - // if there is no specialization, the 'fallback overload' will be taken by converting 0 to long - return to_json_fn::impl(std::forward(val), 0); - } }; struct from_json_fn { - private: template - static constexpr auto impl(Json const &j, T &val, - long) noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val)) + constexpr auto operator()(Json &&j, T &val) const + noexcept(noexcept(from_json(std::forward(j), val))) + -> decltype(from_json(std::forward(j), val)) { - return from_json(j, val); - } - - template - static constexpr auto - impl(Json const &j, T &val, - int) noexcept(noexcept(json_traits::from_json(j, val))) - -> decltype(json_traits::from_json(j, val)) - { - return json_traits::from_json(j, val); - } - - public: - template - constexpr auto operator()(Json const &j, T &val) const - noexcept(noexcept(from_json_fn::impl(j, val, 0))) - -> decltype(from_json_fn::impl(j, val, 0)) - { - return from_json_fn::impl(j, val, 0); + return from_json(std::forward(j), val); } }; @@ -265,6 +214,32 @@ inline namespace constexpr auto const& from_json = _static_const::value; } +// default JSONSerializer template argument +// will use ADL for serialization +struct adl_serializer +{ + template >::value>> + static auto from_json(Json&& j) -> uncvref_t(j), std::declval()), std::declval())> + { + uncvref_t ret; + ::nlohmann::from_json(std::forward(j), ret); + return ret; + } + + template + static auto from_json(Json&& j, T& val) -> decltype(::nlohmann::from_json(std::forward(j), val)) + { + ::nlohmann::from_json(std::forward(j), val); + } + + template + static auto to_json(T&& val) -> decltype(::nlohmann::to_json(std::forward(val))) + { + return ::nlohmann::to_json(std::forward(val)); + } +}; + + /*! @brief a class to store JSON values @@ -352,7 +327,8 @@ template < class NumberIntegerType = std::int64_t, class NumberUnsignedType = std::uint64_t, class NumberFloatType = double, - template class AllocatorType = std::allocator + template class AllocatorType = std::allocator, + class JSONSerializer = adl_serializer > class basic_json { @@ -1421,30 +1397,10 @@ class basic_json assert_invariant(); } - // constructor chosen for user-defined types that either have: - // - a to_json free function in their type's namespace - // - a json_traits specialization for their type - // - // If there is both a free function and a specialization, the latter will be chosen, - // since it is a more advanced use - // - // note: constructor is marked explicit to avoid the following issue: - // - // struct not_equality_comparable{}; - // - // not_equality_comparable{} == not_equality_comparable{}; - // - // this will construct implicitely 2 json objects and call operator== on them - // which can cause nasty bugs on the user's in json-unrelated code - // - // the trade-off is expressiveness in initializer-lists - // auto j = json{{"a", json(not_equality_comparable{})}}; - // - // we can remove this constraint though, since lots of ctor are not explicit already - template >()))> + // constructor chosen when JSONSerializer::to_json exists for type T + template >()))> explicit basic_json(T &&val) - : basic_json(::nlohmann::to_json(std::forward(val))) {} + : basic_json(JSONSerializer::to_json(std::forward(val))) {} /*! @brief create a string (explicit) @@ -3115,16 +3071,11 @@ class basic_json return get_impl(static_cast(nullptr)); } - template - auto get() const -> remove_reference_t< - decltype(::nlohmann::from_json(*this, std::declval()), - std::declval())> + template >::value, float>> + auto get() const -> remove_reference_t()), std::declval())> { - static_assert(std::is_default_constructible::value, - "ValueType must be default-constructible when user-defined " - "from_json method is used"); - ValueType ret; - ::nlohmann::from_json(*this, ret); + uncvref_t ret; + JSONSerializer::from_json(*this, ret); return ret; } diff --git a/test/src/unit-constructor3.cpp b/test/src/unit-constructor3.cpp index de52762f67..77b31c6acf 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 @@ class optional_type 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,107 +141,8 @@ 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]") { SECTION("empty type") @@ -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 33abccf8476cb6e4f330a3728ffd61fe24e5fb39 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 10/88] 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 8dc9b383de..bd59e83b6d 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -214,8 +214,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>> @@ -328,7 +329,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 { @@ -1398,9 +1399,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) @@ -3072,10 +3073,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 837b81d6722da33c83b60668eb2ce3ae74a2b9c7 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 11/88] 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 8279b157e5..a988f47be5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -15,7 +15,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" @@ -36,6 +35,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 2bc685f6b4ae2f53d80eaf526a073244c060a80b 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 12/88] 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 | 49 +++++++++++++++++++------------------------ test/src/unit-udt.cpp | 35 +++++++++++++++++-------------- 2 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index bd59e83b6d..4f4710707b 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -155,22 +155,21 @@ 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)); + return 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); } @@ -219,28 +218,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 @@ -1399,9 +1389,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) @@ -3072,11 +3064,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 77b31c6acf..17494a45c3 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -70,32 +70,31 @@ class optional_type // 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 178441cdfd12a8b7f4c49778259470bd40d4f32a 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 13/88] add basic test for custom serializer --- src/json.hpp | 66 ++++++++++++++++++++++++++++++++++--------- test/src/unit-udt.cpp | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 13 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 4f4710707b..d8f3375995 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -150,6 +150,38 @@ struct has_mapped_type std::is_integral()))>::value; }; +template