From c847e0eea2667d78936bc032884657911b028003 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20DELRIEU?= Date: Sun, 8 Jan 2017 16:17:47 +0100 Subject: [PATCH] replace constructor by from/to_json: array_t - tweaked a bit how `get>` is handled - added a from_json overload for forward list --- src/json.hpp | 196 ++++++++++++++++++++-------------- src/json.hpp.re2c | 196 ++++++++++++++++++++-------------- test/src/unit-conversions.cpp | 2 + test/src/unit-udt.cpp | 26 ++--- 4 files changed, 249 insertions(+), 171 deletions(-) diff --git a/src/json.hpp b/src/json.hpp index 74e6338f..a7be5c81 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -39,6 +39,7 @@ SOFTWARE. #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul #include // strlen +#include // forward_list #include // function, hash, less #include // initializer_list #include // setw @@ -272,6 +273,32 @@ struct external_constructor } }; +template <> +struct external_constructor +{ + template + static void construct(Json &j, const typename Json::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(Json &j, const CompatibleArrayType &arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = + j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } +}; + // very useful construct against boilerplate (more boilerplate needed than in // C++17: http://en.cppreference.com/w/cpp/types/void_t) template struct make_void @@ -432,8 +459,6 @@ template struct is_compatible_basic_json_type { static auto constexpr value = - std::is_same::value or - is_compatible_array_type::value or is_compatible_object_type::value; }; @@ -575,6 +600,17 @@ void to_json(Json &j, UnscopedEnumType e) external_constructor::construct(j, e); } +template < + typename Json, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type::value or + std::is_same::value, + int> = 0> +void to_json(Json &j, CompatibleArrayType const &arr) +{ + external_constructor::construct(j, arr); +} + template void from_json(Json const& j, typename Json::boolean_t& b) { @@ -618,6 +654,59 @@ void from_json(Json const &j, UnscopedEnumType& e) e = static_cast(val); } +template +void from_json(Json const &j, typename Json::array_t &arr) +{ + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + arr = *const_cast(j).template get_ptr(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template +void from_json(Json const&j, std::forward_list& l) +{ + // do not perform the check when user wants to retrieve jsons + // (except when it's null.. ?) + if (j.is_null()) + throw std::domain_error("type must be array, but is " + type_name(j)); + if (not std::is_same::value) + { + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + } + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + l.push_front(it->template get()); +} + +template < + typename Json, typename CompatibleArrayType, + enable_if_t::value and + not std::is_same::value, + int> = 0> +void from_json(Json const &j, CompatibleArrayType &arr) +{ + if (j.is_null()) + throw std::domain_error("type must be array, but is " + type_name(j)); + // when T == Json, do not check if value_t is correct + if (not std::is_same::value) + { + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + } + + using std::begin; + using std::end; + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](Json const &i) + { + // get() returns *this, this won't call a from_json method when + // value_type is Json + return i.template get(); + }); +} + // overload for arithmetic types, not chosen for basic_json template arguments (BooleanType, etc..) // // note: Is it really necessary to provide explicit overloads for boolean_t etc.. @@ -1780,69 +1869,6 @@ class basic_json assert_invariant(); } - /*! - @brief create an array (explicit) - - Create an array JSON value with a given content. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} - - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers - - @since version 1.0.0 - */ - basic_json(const array_t& val) - : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an array (implicit) - - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. - - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} - - @sa @ref basic_json(const array_t&) -- create an array value - - @since version 1.0.0 - */ - template ::value, - int> = 0> - basic_json(const CompatibleArrayType& val) : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create(begin(val), end(val)); - assert_invariant(); - } - - // constructor chosen when: // - JSONSerializer::to_json exists for type T // - T is not a istream, nor convertible to basic_json (float, vectors, etc) @@ -1851,6 +1877,7 @@ class basic_json typename T, enable_if_t>::value and not detail::is_basic_json_nested_class, basic_json_t, primitive_iterator_t>::value and + not std::is_same, basic_json_t>::value and not std::is_same, typename basic_json_t::array_t::iterator>::value and not std::is_same, typename basic_json_t::object_t::iterator>::value and detail::conjunction(nullptr)); } + // if T is basic_json, simply returns *this + template ::value, int> = 0> + basic_json get() const + { + return *this; + } + template < typename T, - enable_if_t, basic_json_t>>, - detail::has_from_json>>::value, - int> = 0 > - auto get() const -> uncvref_t + enable_if_t, basic_json_t>>, + detail::has_from_json>>::value and + not std::is_same>::value, + int> = 0> + // do we really want the uncvref ? if a user call get, shouldn't we static assert ? + auto get() const -> uncvref_t { - 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; + 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; } // This overload is chosen for non-default constructible user-defined-types diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index c4182652..a5674238 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -39,6 +39,7 @@ SOFTWARE. #include // int64_t, uint64_t #include // strtod, strtof, strtold, strtoul #include // strlen +#include // forward_list #include // function, hash, less #include // initializer_list #include // setw @@ -272,6 +273,32 @@ struct external_constructor } }; +template <> +struct external_constructor +{ + template + static void construct(Json &j, const typename Json::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template ::value, + int> = 0> + static void construct(Json &j, const CompatibleArrayType &arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = + j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } +}; + // very useful construct against boilerplate (more boilerplate needed than in // C++17: http://en.cppreference.com/w/cpp/types/void_t) template struct make_void @@ -432,8 +459,6 @@ template struct is_compatible_basic_json_type { static auto constexpr value = - std::is_same::value or - is_compatible_array_type::value or is_compatible_object_type::value; }; @@ -575,6 +600,17 @@ void to_json(Json &j, UnscopedEnumType e) external_constructor::construct(j, e); } +template < + typename Json, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type::value or + std::is_same::value, + int> = 0> +void to_json(Json &j, CompatibleArrayType const &arr) +{ + external_constructor::construct(j, arr); +} + template void from_json(Json const& j, typename Json::boolean_t& b) { @@ -618,6 +654,59 @@ void from_json(Json const &j, UnscopedEnumType& e) e = static_cast(val); } +template +void from_json(Json const &j, typename Json::array_t &arr) +{ + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + arr = *const_cast(j).template get_ptr(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template +void from_json(Json const&j, std::forward_list& l) +{ + // do not perform the check when user wants to retrieve jsons + // (except when it's null.. ?) + if (j.is_null()) + throw std::domain_error("type must be array, but is " + type_name(j)); + if (not std::is_same::value) + { + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + } + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + l.push_front(it->template get()); +} + +template < + typename Json, typename CompatibleArrayType, + enable_if_t::value and + not std::is_same::value, + int> = 0> +void from_json(Json const &j, CompatibleArrayType &arr) +{ + if (j.is_null()) + throw std::domain_error("type must be array, but is " + type_name(j)); + // when T == Json, do not check if value_t is correct + if (not std::is_same::value) + { + if (!j.is_array()) + throw std::domain_error("type must be array, but is " + type_name(j)); + } + + using std::begin; + using std::end; + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](Json const &i) + { + // get() returns *this, this won't call a from_json method when + // value_type is Json + return i.template get(); + }); +} + // overload for arithmetic types, not chosen for basic_json template arguments (BooleanType, etc..) // // note: Is it really necessary to provide explicit overloads for boolean_t etc.. @@ -1781,69 +1870,6 @@ class basic_json assert_invariant(); } - /*! - @brief create an array (explicit) - - Create an array JSON value with a given content. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with an @ref array_t - parameter.,basic_json__array_t} - - @sa @ref basic_json(const CompatibleArrayType&) -- create an array value - from a compatible STL containers - - @since version 1.0.0 - */ - basic_json(const array_t& val) - : m_type(value_t::array), m_value(val) - { - assert_invariant(); - } - - /*! - @brief create an array (implicit) - - Create an array JSON value with a given content. This constructor allows - any type @a CompatibleArrayType that can be used to construct values of - type @ref array_t. - - @tparam CompatibleArrayType An object type whose `value_type` is - compatible to @ref array_t. Examples include `std::vector`, `std::deque`, - `std::list`, `std::forward_list`, `std::array`, `std::set`, - `std::unordered_set`, `std::multiset`, and `unordered_multiset` with a - `value_type` from which a @ref basic_json value can be constructed. - - @param[in] val a value for the array - - @complexity Linear in the size of the passed @a val. - - @throw std::bad_alloc if allocation for array value fails - - @liveexample{The following code shows the constructor with several - compatible array type parameters.,basic_json__CompatibleArrayType} - - @sa @ref basic_json(const array_t&) -- create an array value - - @since version 1.0.0 - */ - template ::value, - int> = 0> - basic_json(const CompatibleArrayType& val) : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create(begin(val), end(val)); - assert_invariant(); - } - - // constructor chosen when: // - JSONSerializer::to_json exists for type T // - T is not a istream, nor convertible to basic_json (float, vectors, etc) @@ -1852,6 +1878,7 @@ class basic_json typename T, enable_if_t>::value and not detail::is_basic_json_nested_class, basic_json_t, primitive_iterator_t>::value and + not std::is_same, basic_json_t>::value and not std::is_same, typename basic_json_t::array_t::iterator>::value and not std::is_same, typename basic_json_t::object_t::iterator>::value and detail::conjunction(nullptr)); } + // if T is basic_json, simply returns *this + template ::value, int> = 0> + basic_json get() const + { + return *this; + } + template < typename T, - enable_if_t, basic_json_t>>, - detail::has_from_json>>::value, - int> = 0 > - auto get() const -> uncvref_t + enable_if_t, basic_json_t>>, + detail::has_from_json>>::value and + not std::is_same>::value, + int> = 0> + // do we really want the uncvref ? if a user call get, shouldn't we static assert ? + auto get() const -> uncvref_t { - 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; + 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; } // This overload is chosen for non-default constructible user-defined-types diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp index b82127bb..2b570533 100644 --- a/test/src/unit-conversions.cpp +++ b/test/src/unit-conversions.cpp @@ -1004,6 +1004,8 @@ TEST_CASE("value conversion") CHECK_THROWS_AS((json().get>()), std::logic_error); CHECK_THROWS_AS((json().get>()), std::logic_error); + // does type really must be an array? or it rather must not be null? + // that's what I thought when other test like this one broke CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index 382b0ed5..bb150e76 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -391,19 +391,19 @@ TEST_CASE("adl_serializer specialization", "[udt]") namespace nlohmann { -// this might work in the future, not in the scope of this PR though -// we have to make this very clear in the doc -template -struct adl_serializer> -{ - static void to_json(json& j, std::vector const& opt) - { - } - - static void from_json(json const& j, std::vector& opt) - { - } -}; + // TODO provide a real example, since this works now :) +// template +// struct adl_serializer> +// { +// static void to_json(json& j, std::vector const& opt) +// { +// +// } +// +// static void from_json(json const& j, std::vector& opt) +// { +// } +// }; } TEST_CASE("current supported types are preferred over specializations", "[udt]")