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 <cstdint> // int64_t, uint64_t #include <cstdlib> // strtod, strtof, strtold, strtoul #include <cstring> // strlen +#include <forward_list> // forward_list #include <functional> // function, hash, less #include <initializer_list> // initializer_list #include <iomanip> // setw @@ -272,6 +273,32 @@ struct external_constructor<value_t::number_integer> } }; +template <> +struct external_constructor<value_t::array> +{ + template <typename Json> + 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 <typename Json, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, + typename Json::array_t>::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<typename Json::array_t>(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 <typename...> struct make_void @@ -432,8 +459,6 @@ template <typename T, typename BasicJson> struct is_compatible_basic_json_type { static auto constexpr value = - std::is_same<T, BasicJson>::value or - is_compatible_array_type<BasicJson, T>::value or is_compatible_object_type<typename BasicJson::object_t, T>::value; }; @@ -575,6 +600,17 @@ void to_json(Json &j, UnscopedEnumType e) external_constructor<value_t::number_integer>::construct(j, e); } +template < + typename Json, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type<Json, CompatibleArrayType>::value or + std::is_same<typename Json::array_t, CompatibleArrayType>::value, + int> = 0> +void to_json(Json &j, CompatibleArrayType const &arr) +{ + external_constructor<value_t::array>::construct(j, arr); +} + template <typename Json> 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<UnscopedEnumType>(val); } +template <typename Json> +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<Json&>(j).template get_ptr<typename Json::array_t*>(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template <typename Json, typename T, typename Allocator> +void from_json(Json const&j, std::forward_list<T, Allocator>& 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<T, Json>::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<T>()); +} + +template < + typename Json, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<Json, CompatibleArrayType>::value and + not std::is_same<typename Json::array_t, + CompatibleArrayType>::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<typename CompatibleArrayType::value_type, Json>::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<Json>() returns *this, this won't call a from_json method when + // value_type is Json + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + // 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 <class CompatibleArrayType, - enable_if_t<detail::is_compatible_array_type<basic_json_t, CompatibleArrayType>::value, - int> = 0> - basic_json(const CompatibleArrayType& val) : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create<array_t>(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<not std::is_base_of<std::istream, uncvref_t<T>>::value and not detail::is_basic_json_nested_class<uncvref_t<T>, basic_json_t, primitive_iterator_t>::value and + not std::is_same<uncvref_t<T>, basic_json_t>::value and not std::is_same<uncvref_t<T>, typename basic_json_t::array_t::iterator>::value and not std::is_same<uncvref_t<T>, typename basic_json_t::object_t::iterator>::value and detail::conjunction<detail::negation<detail::is_compatible_basic_json_type< @@ -3211,23 +3238,34 @@ class basic_json return get_impl(static_cast<ValueType *>(nullptr)); } + // if T is basic_json, simply returns *this + template <typename T, + enable_if_t<std::is_same<T, basic_json_t>::value, int> = 0> + basic_json get() const + { + return *this; + } + template < typename T, - enable_if_t<detail::conjunction<detail::negation<detail::is_compatible_basic_json_type< - uncvref_t<T>, basic_json_t>>, - detail::has_from_json<JSONSerializer, basic_json_t, - uncvref_t<T>>>::value, - int> = 0 > - auto get() const -> uncvref_t<T> + enable_if_t<detail::conjunction< + detail::negation<detail::is_compatible_basic_json_type< + uncvref_t<T>, basic_json_t>>, + detail::has_from_json<JSONSerializer, basic_json_t, + uncvref_t<T>>>::value and + not std::is_same<basic_json_t, uncvref_t<T>>::value, + int> = 0> + // do we really want the uncvref ? if a user call get<int &>, shouldn't we static assert ? + auto get() const -> uncvref_t<T> { - using type = uncvref_t<T>; - static_assert(std::is_default_constructible<type>::value&& - std::is_copy_constructible<type>::value, - "user-defined types must be DefaultConstructible and " - "CopyConstructible when used with get"); - type ret; - JSONSerializer<type>::from_json(*this, ret); - return ret; + using type = uncvref_t<T>; + static_assert(std::is_default_constructible<type>::value && + std::is_copy_constructible<type>::value, + "user-defined types must be DefaultConstructible and " + "CopyConstructible when used with get"); + type ret; + JSONSerializer<type>::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 <cstdint> // int64_t, uint64_t #include <cstdlib> // strtod, strtof, strtold, strtoul #include <cstring> // strlen +#include <forward_list> // forward_list #include <functional> // function, hash, less #include <initializer_list> // initializer_list #include <iomanip> // setw @@ -272,6 +273,32 @@ struct external_constructor<value_t::number_integer> } }; +template <> +struct external_constructor<value_t::array> +{ + template <typename Json> + 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 <typename Json, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, + typename Json::array_t>::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<typename Json::array_t>(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 <typename...> struct make_void @@ -432,8 +459,6 @@ template <typename T, typename BasicJson> struct is_compatible_basic_json_type { static auto constexpr value = - std::is_same<T, BasicJson>::value or - is_compatible_array_type<BasicJson, T>::value or is_compatible_object_type<typename BasicJson::object_t, T>::value; }; @@ -575,6 +600,17 @@ void to_json(Json &j, UnscopedEnumType e) external_constructor<value_t::number_integer>::construct(j, e); } +template < + typename Json, typename CompatibleArrayType, + enable_if_t< + is_compatible_array_type<Json, CompatibleArrayType>::value or + std::is_same<typename Json::array_t, CompatibleArrayType>::value, + int> = 0> +void to_json(Json &j, CompatibleArrayType const &arr) +{ + external_constructor<value_t::array>::construct(j, arr); +} + template <typename Json> 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<UnscopedEnumType>(val); } +template <typename Json> +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<Json&>(j).template get_ptr<typename Json::array_t*>(); +} + +// forward_list doesn't have an insert method, TODO find a way to avoid including forward_list +template <typename Json, typename T, typename Allocator> +void from_json(Json const&j, std::forward_list<T, Allocator>& 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<T, Json>::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<T>()); +} + +template < + typename Json, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<Json, CompatibleArrayType>::value and + not std::is_same<typename Json::array_t, + CompatibleArrayType>::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<typename CompatibleArrayType::value_type, Json>::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<Json>() returns *this, this won't call a from_json method when + // value_type is Json + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + // 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 <class CompatibleArrayType, - enable_if_t<detail::is_compatible_array_type<basic_json_t, CompatibleArrayType>::value, - int> = 0> - basic_json(const CompatibleArrayType& val) : m_type(value_t::array) - { - using std::begin; - using std::end; - m_value.array = create<array_t>(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<not std::is_base_of<std::istream, uncvref_t<T>>::value and not detail::is_basic_json_nested_class<uncvref_t<T>, basic_json_t, primitive_iterator_t>::value and + not std::is_same<uncvref_t<T>, basic_json_t>::value and not std::is_same<uncvref_t<T>, typename basic_json_t::array_t::iterator>::value and not std::is_same<uncvref_t<T>, typename basic_json_t::object_t::iterator>::value and detail::conjunction<detail::negation<detail::is_compatible_basic_json_type< @@ -3209,23 +3236,34 @@ class basic_json return get_impl(static_cast<ValueType *>(nullptr)); } + // if T is basic_json, simply returns *this + template <typename T, + enable_if_t<std::is_same<T, basic_json_t>::value, int> = 0> + basic_json get() const + { + return *this; + } + template < typename T, - enable_if_t<detail::conjunction<detail::negation<detail::is_compatible_basic_json_type< - uncvref_t<T>, basic_json_t>>, - detail::has_from_json<JSONSerializer, basic_json_t, - uncvref_t<T>>>::value, - int> = 0 > - auto get() const -> uncvref_t<T> + enable_if_t<detail::conjunction< + detail::negation<detail::is_compatible_basic_json_type< + uncvref_t<T>, basic_json_t>>, + detail::has_from_json<JSONSerializer, basic_json_t, + uncvref_t<T>>>::value and + not std::is_same<basic_json_t, uncvref_t<T>>::value, + int> = 0> + // do we really want the uncvref ? if a user call get<int &>, shouldn't we static assert ? + auto get() const -> uncvref_t<T> { - using type = uncvref_t<T>; - static_assert(std::is_default_constructible<type>::value&& - std::is_copy_constructible<type>::value, - "user-defined types must be DefaultConstructible and " - "CopyConstructible when used with get"); - type ret; - JSONSerializer<type>::from_json(*this, ret); - return ret; + using type = uncvref_t<T>; + static_assert(std::is_default_constructible<type>::value && + std::is_copy_constructible<type>::value, + "user-defined types must be DefaultConstructible and " + "CopyConstructible when used with get"); + type ret; + JSONSerializer<type>::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::vector<json>>()), std::logic_error); CHECK_THROWS_AS((json().get<std::list<json>>()), 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<std::list<int>>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get<std::vector<int>>()), "type must be array, but is null"); CHECK_THROWS_WITH((json().get<std::vector<json>>()), "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 <typename T> -struct adl_serializer<std::vector<T>> -{ - static void to_json(json& j, std::vector<T> const& opt) - { - } - - static void from_json(json const& j, std::vector<T>& opt) - { - } -}; + // TODO provide a real example, since this works now :) +// template <typename T> +// struct adl_serializer<std::vector<T>> +// { +// static void to_json(json& j, std::vector<T> const& opt) +// { +// +// } +// +// static void from_json(json const& j, std::vector<T>& opt) +// { +// } +// }; } TEST_CASE("current supported types are preferred over specializations", "[udt]")