diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 1706cbdc6..2ff3b95e6 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -88,6 +88,9 @@ using to_json_function = decltype(T::to_json(std::declval()...)); template using from_json_function = decltype(T::from_json(std::declval()...)); +template +using try_deserialize_function = decltype(T::try_deserialize(std::declval()...)); + template using get_template_function = decltype(std::declval().template get()); @@ -131,6 +134,20 @@ struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_jso const BasicJsonType&>::value; }; +template +struct has_try_deserialize : std::false_type {}; + +template +struct has_try_deserialize < BasicJsonType, T, + enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected::value; +}; + // This trait checks if BasicJsonType::json_serializer::to_json exists // Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. template diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 06ff30329..7aa153a67 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -3068,6 +3068,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return JSONSerializer::from_json(*this); } + /*! + @brief get a wrapped value (explicit) + + Explicit type conversion between the JSON value and a compatible value wrapper + The value is converted by calling the @ref json_serializer + `try_deserialize()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::try_deserialize(*this); + @endcode + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType wrapper + + @throw what @ref json_serializer `try_deserialize()` method throws + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + typename ReturnType = decltype(JSONSerializer::try_deserialize(std::declval())), + detail::enable_if_t < !std::is_same::value && + detail::has_try_deserialize::value, + int > = 0 > + ReturnType try_get() const noexcept(noexcept( + JSONSerializer::try_deserialize(std::declval()))) + { + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::try_deserialize(*this); + } + /*! @brief get a value (explicit) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 7ef4befb2..5c71bd8b8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3465,6 +3465,9 @@ using to_json_function = decltype(T::to_json(std::declval()...)); template using from_json_function = decltype(T::from_json(std::declval()...)); +template +using try_deserialize_function = decltype(T::try_deserialize(std::declval()...)); + template using get_template_function = decltype(std::declval().template get()); @@ -3508,6 +3511,20 @@ struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_jso const BasicJsonType&>::value; }; +template +struct has_try_deserialize : std::false_type {}; + +template +struct has_try_deserialize < BasicJsonType, T, + enable_if_t < !is_basic_json::value >> +{ + using serializer = typename BasicJsonType::template json_serializer; + + static constexpr bool value = + is_detected::value; +}; + // This trait checks if BasicJsonType::json_serializer::to_json exists // Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. template @@ -19957,6 +19974,38 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec return JSONSerializer::from_json(*this); } + /*! + @brief get a wrapped value (explicit) + + Explicit type conversion between the JSON value and a compatible value wrapper + The value is converted by calling the @ref json_serializer + `try_deserialize()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::try_deserialize(*this); + @endcode + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType wrapper + + @throw what @ref json_serializer `try_deserialize()` method throws + */ + template < typename ValueTypeCV, typename ValueType = detail::uncvref_t, + typename ReturnType = decltype(JSONSerializer::try_deserialize(std::declval())), + detail::enable_if_t < !std::is_same::value && + detail::has_try_deserialize::value, + int > = 0 > + ReturnType try_get() const noexcept(noexcept( + JSONSerializer::try_deserialize(std::declval()))) + { + static_assert(!std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::try_deserialize(*this); + } + /*! @brief get a value (explicit) diff --git a/test/src/unit-udt.cpp b/test/src/unit-udt.cpp index 2bebd8f59..5d79b859c 100644 --- a/test/src/unit-udt.cpp +++ b/test/src/unit-udt.cpp @@ -542,6 +542,105 @@ TEST_CASE("Non-copyable types" * doctest::test_suite("udt")) } } +namespace udt +{ +struct book +{ + std::shared_ptr m_author; + std::string m_content; + book(std::shared_ptr a, const std::string& c) : m_author(a), m_content(c) {} +}; + +// operators +static bool operator==(const std::shared_ptr& lhs, const std::shared_ptr& rhs) +{ + if (!lhs && !rhs) + { + return true; + } + + if (!lhs || ! rhs) + { + return false; + } + + return *lhs == *rhs; +} + +static bool operator==(const book& lhs, const book& rhs) +{ + return std::tie(lhs.m_author, lhs.m_content) == + std::tie(rhs.m_author, rhs.m_content); +} +} + +namespace nlohmann +{ +template <> +struct adl_serializer +{ + static void to_json(json& j, const udt::book& opt) + { + j["author"] = opt.m_author; + j["content"] = opt.m_content; + } + + // this is the overload needed for non-copyable types, + static std::unique_ptr try_deserialize(const json& j) + { + if (j.is_null()) + { + return nullptr; + } + else + { + auto author = j["author"].get>(); + + if (!j.contains("content") || j["content"].is_null()) + { + return nullptr; + } + + auto content = j["content"].get(); + + return std::unique_ptr(new udt::book(author, content)); + } + } +}; +} + +TEST_CASE("try_deserialize" * doctest::test_suite("udt")) +{ + SECTION("from valid object") + { + auto person = udt::person{{42}, {"John Doe"}, udt::country::russia}; + auto content = std::string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit."}; + udt::book book{std::shared_ptr(new udt::person(person)), content}; + + json j = book; + + auto optBook = j.try_get(); + REQUIRE(optBook); + CHECK(*optBook == book); + } + + SECTION("from null") + { + json j = nullptr; + + auto optBook = j.try_get(); + REQUIRE(!optBook); + } + + SECTION("from invalid object") + { + json j = R"({"author" : {"age":23, "name":"theo", "country":"France"}})"_json; + + auto optBook = j.try_get(); + REQUIRE(!optBook); + } +} + // custom serializer - advanced usage // pack structs that are pod-types (but not scalar types) // relies on adl for any other type