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] 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 2c1fd6585..05fb8c922 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 62213ad31..8279b157e 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 000000000..d119625fa --- /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); + } + } +}