From 41f1d000db830d166d23c69f47edc72a792e3d45 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Mon, 15 Jul 2024 10:44:57 +0300 Subject: [PATCH 1/3] value_from supports conversion through helper types --- include/boost/json/conversion.hpp | 8 +- include/boost/json/detail/parse_into.hpp | 3 +- include/boost/json/detail/value_from.hpp | 31 +++-- include/boost/json/detail/value_to.hpp | 4 +- include/boost/json/fwd.hpp | 2 +- include/boost/json/impl/conversion.hpp | 106 +++++++++++++---- include/boost/json/impl/serializer.hpp | 2 +- include/boost/json/value_from.hpp | 14 ++- include/boost/json/value_to.hpp | 25 ++-- test/value_from.cpp | 142 +++++++++++++++++++++++ 10 files changed, 285 insertions(+), 52 deletions(-) diff --git a/include/boost/json/conversion.hpp b/include/boost/json/conversion.hpp index 1d30b3a29..87db9fb1f 100644 --- a/include/boost/json/conversion.hpp +++ b/include/boost/json/conversion.hpp @@ -234,7 +234,7 @@ struct is_sequence_like; @see @ref value_from, @ref value_to */ -template +template struct is_map_like; /** Determine if `T` can be treated like a tuple during conversions. @@ -437,6 +437,12 @@ struct is_variant_like; template struct is_optional_like; +template< class T, class Context = void > +struct represent_as; + +template< class T, class Context = void > +using represent_as_t = typename represent_as::type; + } // namespace json } // namespace boost diff --git a/include/boost/json/detail/parse_into.hpp b/include/boost/json/detail/parse_into.hpp index 3f7418a2c..c7966ebb5 100644 --- a/include/boost/json/detail/parse_into.hpp +++ b/include/boost/json/detail/parse_into.hpp @@ -79,7 +79,8 @@ class converting_handler; // get_handler template< class V, class P > -using get_handler = converting_handler< generic_conversion_category, V, P >; +using get_handler = converting_handler< + generic_conversion_category, V, P>; template class handler_error_base { diff --git a/include/boost/json/detail/value_from.hpp b/include/boost/json/detail/value_from.hpp index 0c9b799b2..00b5fe5cb 100644 --- a/include/boost/json/detail/value_from.hpp +++ b/include/boost/json/detail/value_from.hpp @@ -22,9 +22,12 @@ namespace boost { namespace json { - namespace detail { +template< class Ctx, class T > +using value_from_attrs = conversion_attrs< + Ctx, remove_cvref, value_from_conversion>; + template< class Ctx, class T > struct append_tuple_element { array& arr; @@ -41,6 +44,21 @@ struct append_tuple_element { } }; +template< class T, class Ctx > +using to_representation_result = mp11::mp_if< + std::is_same< + remove_cvref, typename value_from_attrs::representation >, + T&&, + typename value_from_attrs::representation>; + +template< class Ctx, class T > +to_representation_result< T, Ctx > +to_representation( T&& t ) +{ + using R = to_representation_result< T, Ctx >; + return static_cast( static_cast(t) ); +} + //---------------------------------------------------------- // User-provided conversion @@ -107,9 +125,11 @@ value_from_impl( map_like_conversion_tag, value& jv, T&& from, Ctx const& ctx ) object& obj = jv.emplace_object(); obj.reserve(detail::try_size(from, size_implementation())); for (auto&& elem : from) + { obj.emplace( - get<0>(elem), + to_representation( get<0>(elem) ), value_from( get<1>(elem), ctx, obj.storage() )); + } } // ranges @@ -257,13 +277,6 @@ value_from_impl( path_conversion_tag, value& jv, T&& from, Ctx const& ) jv.emplace_string().assign(sv); } -//---------------------------------------------------------- -// Contextual conversions - -template< class Ctx, class T > -using value_from_category = conversion_category< - Ctx, T, value_from_conversion >; - } // detail #ifndef BOOST_NO_CXX17_HDR_OPTIONAL diff --git a/include/boost/json/detail/value_to.hpp b/include/boost/json/detail/value_to.hpp index 29ae444f9..1f4ee591b 100644 --- a/include/boost/json/detail/value_to.hpp +++ b/include/boost/json/detail/value_to.hpp @@ -818,8 +818,8 @@ value_to_impl( Impl impl, value_to_tag, value const& jv, Ctx const& ctx ) } template< class Ctx, class T > -using value_to_category = conversion_category< - Ctx, T, value_to_conversion >; +using value_to_attrs = conversion_attrs< + Ctx, remove_cvref, value_to_conversion>; } // detail diff --git a/include/boost/json/fwd.hpp b/include/boost/json/fwd.hpp index fa6dd7152..1726451a9 100644 --- a/include/boost/json/fwd.hpp +++ b/include/boost/json/fwd.hpp @@ -45,7 +45,7 @@ struct is_string_like; template struct is_sequence_like; -template +template struct is_map_like; template diff --git a/include/boost/json/impl/conversion.hpp b/include/boost/json/impl/conversion.hpp index 6c045ea01..ec1eadde4 100644 --- a/include/boost/json/impl/conversion.hpp +++ b/include/boost/json/impl/conversion.hpp @@ -32,6 +32,9 @@ namespace boost { namespace json { namespace detail { +struct no_context +{}; + #ifdef __cpp_lib_nonmember_container_access using std::size; #endif @@ -317,14 +320,14 @@ using native_conversion_category = mp11::mp_cond< std::is_same, string_conversion_tag>; // generic conversions -template< class T > +template< class T, class Ctx > using generic_conversion_category = mp11::mp_cond< std::is_same, bool_conversion_tag, std::is_integral, integral_conversion_tag, std::is_floating_point, floating_point_conversion_tag, is_null_like, null_like_conversion_tag, is_string_like, string_like_conversion_tag, - is_map_like, map_like_conversion_tag, + is_map_like, map_like_conversion_tag, is_sequence_like, sequence_conversion_tag, is_tuple_like, tuple_conversion_tag, is_described_class, described_class_conversion_tag, @@ -338,38 +341,92 @@ using generic_conversion_category = mp11::mp_cond< template< class T > using nested_type = typename T::type; template< class T1, class T2 > -using conversion_category_impl_helper = mp11::mp_eval_if_not< +using conversion_category_helper = mp11::mp_eval_if_not< std::is_same, T1, mp11::mp_eval_or_q, T1, mp11::mp_quote, T2>; + +template< class Ctx > +using fix_context = mp11::mp_if< std::is_same, void, Ctx>; + +template +using representation_or_void = mp11::mp_eval_or; + +template< class U > +using is_not_void = mp11::mp_bool< !std::is_same::value >; + +template< class T, class Ctxs > +struct representation_helper +{ + using size = mp11::mp_size; + + template< class I > + using exists = mp11::mp_less; + + template< class Ctx > + using step = representation_or_void; + using reps = mp11::mp_transform; + using r_index = mp11::mp_find_if< reps, is_not_void >; + + using type = mp11::mp_eval_if< + mp11::mp_not< exists >, + T, + mp11::mp_at, reps, r_index>; +}; + +template< class T, class Ctx > +struct conversion_representation_impl + : representation_helper< T, mp11::mp_list > +{}; + +template< class T > +struct conversion_representation_impl + : representation_helper< T, mp11::mp_list > +{}; + +template< class T, class... Ctxs > +struct conversion_representation_impl< T, std::tuple > + : representation_helper< T, mp11::mp_list..., void> > +{}; + +template< class T, class Ctx > +using conversion_representation + = typename conversion_representation_impl::type; + template< class Ctx, class T, class Dir > -struct conversion_category_impl +struct conversion_attrs { - using type = mp11::mp_fold< + using representation = conversion_representation; + + using category = mp11::mp_fold< mp11::mp_list< - mp11::mp_defer, - mp11::mp_defer, - mp11::mp_defer>, + mp11::mp_defer, + mp11::mp_defer, + mp11::mp_defer>, no_conversion_tag, - conversion_category_impl_helper>; + conversion_category_helper>; }; -template< class Ctx, class T, class Dir > -using conversion_category = - typename conversion_category_impl< Ctx, T, Dir >::type; template< class T > using any_conversion_tag = mp11::mp_not< std::is_same< T, no_conversion_tag > >; +template< class Ctx, class T, class Dir > +using conversion_category = typename conversion_attrs::category; + template< class T, class Dir, class... Ctxs > -struct conversion_category_impl< std::tuple, T, Dir > +struct conversion_attrs< std::tuple, T, Dir > { + using size = mp11::mp_size_t< sizeof...(Ctxs) >; using ctxs = mp11::mp_list< remove_cvref... >; - using cats = mp11::mp_list< - conversion_category, T, Dir>... >; template< class I > - using exists = mp11::mp_less< I, mp11::mp_size >; + using exists = mp11::mp_less; + + using representation = conversion_representation< T, std::tuple >; + + using cats = mp11::mp_list< + conversion_category, representation, Dir>... >; using context2 = mp11::mp_find< cats, full_context_conversion_tag >; using context1 = mp11::mp_find< cats, context_conversion_tag >; @@ -379,14 +436,11 @@ struct conversion_category_impl< std::tuple, T, Dir > exists, context1, exists, context0, mp11::mp_true, mp11::mp_find_if< cats, any_conversion_tag > >; - using type = mp11::mp_eval_or< + using category = mp11::mp_eval_or< no_conversion_tag, mp11::mp_at, cats, index >; }; -struct no_context -{}; - template using can_convert = mp11::mp_not< std::is_same< @@ -456,10 +510,10 @@ template< class T, class Dir, class... Ctxs > struct supported_context< std::tuple, T, Dir > { using Ctx = std::tuple; - using impl = conversion_category_impl; - using index = typename impl::index; + using Attrs = conversion_attrs; + using index = typename Attrs::index; using next_supported = supported_context< - mp11::mp_at< typename impl::ctxs, index >, T, Dir >; + mp11::mp_at< typename Attrs::ctxs, index >, T, Dir >; using type = typename next_supported::type; static @@ -508,12 +562,14 @@ struct is_sequence_like mp11::mp_valid> { }; -template +template struct is_map_like : mp11::mp_all< is_sequence_like, mp11::mp_valid_and_true, - is_string_like>, + is_string_like< + detail::conversion_representation< + detail::remove_cvref< detail::key_type >, Ctx>>, mp11::mp_valid_and_true> { }; diff --git a/include/boost/json/impl/serializer.hpp b/include/boost/json/impl/serializer.hpp index 203c7ba50..38bb4ec05 100644 --- a/include/boost/json/impl/serializer.hpp +++ b/include/boost/json/impl/serializer.hpp @@ -818,7 +818,7 @@ template bool write_impl(writer& w, stream& ss) { - using cat = detail::generic_conversion_category; + using cat = detail::generic_conversion_category; return write_impl( cat(), w, ss ); } diff --git a/include/boost/json/value_from.hpp b/include/boost/json/value_from.hpp index 21669878e..12ba2d7fc 100644 --- a/include/boost/json/value_from.hpp +++ b/include/boost/json/value_from.hpp @@ -83,11 +83,17 @@ value_from( Context const& ctx, value& jv) { - using bare_T = detail::remove_cvref; + using Attrs = detail::value_from_attrs; BOOST_STATIC_ASSERT(detail::conversion_round_trips< - Context, bare_T, detail::value_from_conversion>::value); - using cat = detail::value_from_category; - detail::value_from_impl( cat(), jv, std::forward(t), ctx ); + Context, + typename Attrs::representation, + detail::value_from_conversion>::value); + + detail::value_from_impl( + typename Attrs::category(), + jv, + detail::to_representation( static_cast(t) ), + ctx ); } /** Convert an object of type `T` to @ref value. diff --git a/include/boost/json/value_to.hpp b/include/boost/json/value_to.hpp index 0006a7dbb..b6b068dc0 100644 --- a/include/boost/json/value_to.hpp +++ b/include/boost/json/value_to.hpp @@ -89,11 +89,16 @@ T value_to( value const& jv, Context const& ctx ) { BOOST_STATIC_ASSERT(! std::is_reference::value); - using bare_T = detail::remove_cvref; + + using Attrs = detail::value_to_attrs; BOOST_STATIC_ASSERT(detail::conversion_round_trips< - Context, bare_T, detail::value_to_conversion>::value); - using cat = detail::value_to_category; - return detail::value_to_impl( cat(), value_to_tag(), jv, ctx ); + Context, + typename Attrs::representation, + detail::value_to_conversion>::value); + + using bare_T = detail::remove_cvref; + return detail::value_to_impl( + typename Attrs::category(), value_to_tag(), jv, ctx); } /** Convert a @ref value to an object of type `T`. @@ -223,12 +228,16 @@ typename result_for::type try_value_to( value const& jv, Context const& ctx ) { BOOST_STATIC_ASSERT(! std::is_reference::value); - using bare_T = detail::remove_cvref; + + using Attrs = detail::value_to_attrs; BOOST_STATIC_ASSERT(detail::conversion_round_trips< - Context, bare_T, detail::value_to_conversion>::value); - using cat = detail::value_to_category; + Context, + typename Attrs::representation, + detail::value_to_conversion>::value); + + using bare_T = detail::remove_cvref; return detail::value_to_impl( - cat(), try_value_to_tag(), jv, ctx ); + typename Attrs::category(), try_value_to_tag(), jv, ctx ); } /** Convert a @ref value to a `boost::system::result`. diff --git a/test/value_from.cpp b/test/value_from.cpp index f5e2a1d50..a36230ce6 100644 --- a/test/value_from.cpp +++ b/test/value_from.cpp @@ -258,6 +258,87 @@ tag_invoke( } +struct id +{ + // msvc 14.0 doesn't like these being references to char arrays + static constexpr char const* id1 = "Id#1"; + static constexpr char const* id2 = "Id#2"; + + std::size_t n; +}; + +bool +operator<(id l, id r) noexcept +{ + return l.n < r.n; +} + +struct id_string_repr +{ + std::size_t n; + + id_string_repr(id x) noexcept + : n(x.n) + {} + + id_string_repr(boost::json::string_view sv) + { + if( sv.data() == id::id1 ) + n = 1; + else if( sv.data() == id::id2 ) + n = 2; + else + n = std::size_t(-1); + } + + operator id() const noexcept + { + return {n}; + } + + operator boost::json::string_view() const noexcept + { + switch(n) + { + case 1: return boost::json::string_view(id::id1, 4); + case 2: return boost::json::string_view(id::id2, 4); + default: return boost::json::string_view("unknown"); + } + } +}; + +struct T14 +{ + id i; +}; +BOOST_DESCRIBE_STRUCT(T14, (), (i)) + +struct as_string {}; + +struct int_as_string +{ + std::string s; + + int_as_string(int n) noexcept + : s( std::to_string(n) ) + {} + + int_as_string(boost::json::string_view sv) + : s(sv) + {} + + operator int() const noexcept + { + return std::atoi( s.data() ); + } + + operator boost::json::string_view() const noexcept + { + return s; + } +}; + + } // namespace value_from_test_ns template @@ -296,6 +377,18 @@ struct is_described_class<::value_from_test_ns::T11> : std::true_type { }; +template<> +struct represent_as<::value_from_test_ns::id> +{ + using type = ::value_from_test_ns::id_string_repr; +}; + +template <> +struct represent_as +{ + using type = ::value_from_test_ns::int_as_string; +}; + namespace { template< class T, class... Context > @@ -404,6 +497,23 @@ class value_from_test value b = value_from( a, ctx... ); BOOST_TEST(b.is_null()); } + { + value jv = value_from( value_from_test_ns::id{1}, ctx... ); + BOOST_TEST( jv == value("Id#1") ); + + jv = value_from( value_from_test_ns::id{2}, ctx... ); + BOOST_TEST( jv == value("Id#2") ); + + jv = value_from( + std::vector{ {1}, {2}, {2}, {1} }, + ctx... ); + BOOST_TEST(( jv == value{"Id#1", "Id#2", "Id#2", "Id#1"} )); + + jv = value_from( + std::tuple{ {1}, 12 }, + ctx... ); + BOOST_TEST(( jv == value{"Id#1", 12} )); + } } template< class... Context > @@ -447,6 +557,12 @@ class value_from_test BOOST_TEST(a.size() == c.as_array().size()); BOOST_TEST(b.as_array().size() == c.as_array().size()); } + { + value jv = value_from( + std::map{ {{1}, 42}, {{2}, 43} }, + ctx... ); + BOOST_TEST(( jv == object{ {"Id#1", 42}, {"Id#2", 43} } )); + } } template< class... Context > @@ -505,6 +621,9 @@ class value_from_test ::value_from_test_ns::E1 e1 = ::value_from_test_ns::E1::a; BOOST_TEST( value_from( e1, ctx... ) == "a" ); + jv = value_from( value_from_test_ns::T14{ {1} }, ctx... ); + BOOST_TEST(( jv == object{ {"i", "Id#1"} } )); + e1 = ::value_from_test_ns::E1::b; BOOST_TEST( value_from( e1, ctx... ) == "b" ); @@ -524,6 +643,11 @@ class value_from_test BOOST_TEST( jv == (value{1, 2, 3, nullptr, 5}) ); BOOST_TEST( value_from( std::nullopt, ctx... ).is_null() ); + + jv = value_from( + std::optional( + value_from_test_ns::id{1} ), ctx... ); + BOOST_TEST( jv == value("Id#1") ); #endif } @@ -546,6 +670,12 @@ class value_from_test jv = value_from( v, ctx... ); BOOST_TEST(jv == "T5"); + jv = value_from( + std::variant( + value_from_test_ns::id{2} ), + ctx... ); + BOOST_TEST( jv == value("Id#2") ); + BOOST_TEST( value() == value_from( std::monostate(), ctx... ) ); #endif // BOOST_NO_CXX17_HDR_VARIANT } @@ -611,6 +741,18 @@ class value_from_test value_from_test_ns::custom_context(), value_from_test_ns::another_context() ) ); BOOST_TEST( jv == (object{ {"1", "T12"}, {"2", "T12"} }) ); + + jv = value_from( + std::map{ {1,2}, {2,4}, {3, 8} }, + value_from_test_ns::as_string() ); + BOOST_TEST( jv == (object{ {"1", "2"}, {"2", "4"}, {"3", "8"} }) ); + + jv = value_from( + std::map{ {1,2}, {2,4}, {3, 6} }, + std::make_tuple( + value_from_test_ns::as_string(), + value_from_test_ns::custom_context() )); + BOOST_TEST( jv == (object{ {"1", "2"}, {"2", "4"}, {"3", "6"} }) ); } struct run_templated_tests From 95664574c47fbeed25c79e7edf06733b38a50a50 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Sat, 3 Aug 2024 08:31:46 +0700 Subject: [PATCH 2/3] (try_)value_to supports conversion through helper types --- include/boost/json/detail/value_to.hpp | 12 +- include/boost/json/value_to.hpp | 17 ++- test/value_to.cpp | 151 +++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 14 deletions(-) diff --git a/include/boost/json/detail/value_to.hpp b/include/boost/json/detail/value_to.hpp index 1f4ee591b..02aaaa046 100644 --- a/include/boost/json/detail/value_to.hpp +++ b/include/boost/json/detail/value_to.hpp @@ -26,6 +26,10 @@ namespace json { namespace detail { +template< class Ctx, class T > +using value_to_attrs = conversion_attrs< + Ctx, remove_cvref, value_to_conversion>; + template using has_reserve_member_helper = decltype(std::declval().reserve(0)); template @@ -232,11 +236,13 @@ value_to_impl( auto ins = detail::inserter(res, inserter_implementation()); for( key_value_pair const& kv: *obj ) { + using A = value_to_attrs< Ctx, key_type >; + using K = typename A::representation; auto elem_res = try_value_to>( kv.value(), ctx ); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = value_type{ - key_type(kv.key()), + K(kv.key()), std::move(*elem_res)}; } return res; @@ -817,10 +823,6 @@ value_to_impl( Impl impl, value_to_tag, value const& jv, Ctx const& ctx ) return value_to_impl(impl, try_value_to_tag(), jv, ctx).value(); } -template< class Ctx, class T > -using value_to_attrs = conversion_attrs< - Ctx, remove_cvref, value_to_conversion>; - } // detail #ifndef BOOST_NO_CXX17_HDR_OPTIONAL diff --git a/include/boost/json/value_to.hpp b/include/boost/json/value_to.hpp index b6b068dc0..cd37c909d 100644 --- a/include/boost/json/value_to.hpp +++ b/include/boost/json/value_to.hpp @@ -91,14 +91,13 @@ value_to( value const& jv, Context const& ctx ) BOOST_STATIC_ASSERT(! std::is_reference::value); using Attrs = detail::value_to_attrs; + using Rep = typename Attrs::representation; BOOST_STATIC_ASSERT(detail::conversion_round_trips< - Context, - typename Attrs::representation, - detail::value_to_conversion>::value); + Context, Rep, detail::value_to_conversion>::value); using bare_T = detail::remove_cvref; - return detail::value_to_impl( - typename Attrs::category(), value_to_tag(), jv, ctx); + return static_cast(detail::value_to_impl( + typename Attrs::category(), value_to_tag(), jv, ctx )); } /** Convert a @ref value to an object of type `T`. @@ -230,14 +229,14 @@ try_value_to( value const& jv, Context const& ctx ) BOOST_STATIC_ASSERT(! std::is_reference::value); using Attrs = detail::value_to_attrs; + using Rep = typename Attrs::representation; BOOST_STATIC_ASSERT(detail::conversion_round_trips< - Context, - typename Attrs::representation, - detail::value_to_conversion>::value); + Context, Rep, detail::value_to_conversion>::value); using bare_T = detail::remove_cvref; return detail::value_to_impl( - typename Attrs::category(), try_value_to_tag(), jv, ctx ); + typename Attrs::category(), try_value_to_tag(), jv, ctx ) + & [](Rep&& rep) { return static_cast(rep); }; } /** Convert a @ref value to a `boost::system::result`. diff --git a/test/value_to.cpp b/test/value_to.cpp index 5d6fcc34a..b0d6c7e9e 100644 --- a/test/value_to.cpp +++ b/test/value_to.cpp @@ -196,6 +196,97 @@ tag_invoke( return T11( jv.to_number() ); } +struct id +{ + static constexpr char const* id1 = "Id#1"; + static constexpr char const* id2 = "Id#2"; + + std::size_t n; +}; + +bool +operator==(id l, id r) noexcept +{ + return l.n == r.n; +} + +bool +operator!=(id l, id r) noexcept +{ + return l.n != r.n; +} + +bool +operator<(id l, id r) noexcept +{ + return l.n < r.n; +} + +struct id_string_repr +{ + std::size_t n; + + id_string_repr(id x) noexcept + : n(x.n) + {} + + id_string_repr(boost::json::string_view sv) + { + if( sv == boost::json::string_view(id::id1, 4) ) + n = 1; + else if( sv == boost::json::string_view(id::id2, 4) ) + n = 2; + else + throw std::runtime_error( "unknown id" ); + } + + operator id() const noexcept + { + return {n}; + } + + operator boost::json::string_view() const noexcept + { + switch(n) + { + case 1: return boost::json::string_view(id::id1, 4); + case 2: return boost::json::string_view(id::id2, 4); + default: return boost::json::string_view("unknown"); + } + } +}; + +struct T12 +{ + id i; +}; +BOOST_DESCRIBE_STRUCT(T12, (), (i)) + +struct as_string {}; + +struct int_as_string +{ + std::string s; + + int_as_string(int n) noexcept + : s( std::to_string(n) ) + {} + + int_as_string(boost::json::string_view sv) + : s(sv) + {} + + operator int() const noexcept + { + return std::atoi( s.data() ); + } + + operator boost::json::string_view() const noexcept + { + return s; + } +}; + } // namespace value_to_test_ns namespace std @@ -225,6 +316,18 @@ struct is_null_like<::value_to_test_ns::T1> : std::true_type { }; template<> struct is_described_class<::value_to_test_ns::T7> : std::true_type { }; +template<> +struct represent_as<::value_to_test_ns::id> +{ + using type = ::value_to_test_ns::id_string_repr; +}; + +template <> +struct represent_as +{ + using type = ::value_to_test_ns::int_as_string; +}; + template struct can_apply_value_to : std::false_type @@ -335,6 +438,19 @@ class value_to_test BOOST_TEST_CONV( arr, ctx... ); } + BOOST_TEST(( + value_to< std::vector >( + value{"Id#1", "Id#2", "Id#2", "Id#1"}, ctx... ) + == std::vector{{1}, {2}, {2}, {1}} )); + BOOST_TEST(( + value_to< std::tuple >( + value{"Id#1", 12}, ctx... ) + == std::tuple{{1}, 12} )); + BOOST_TEST(( + value_to< std::map >( + value{ {"Id#1", 42}, {"Id#2", 43} }, ctx... ) + == std::map{ {{1}, 42}, {{2}, 43} } )); + // mismatched type BOOST_TEST_THROWS_WITH_LOCATION( value_to( value(), ctx... )); @@ -464,6 +580,11 @@ class value_to_test BOOST_TEST( e1 == ::value_to_test_ns::E1::b ); } + BOOST_TEST(( + value_to( + value(object{ {"i", "Id#1"} }), ctx... ).i + == value_to_test_ns::T12{ {1} }.i )); + BOOST_TEST_THROWS_WITH_LOCATION( value_to<::value_to_test_ns::E1>( value(1), ctx... )); BOOST_TEST_THROWS_WITH_LOCATION( @@ -506,6 +627,12 @@ class value_to_test value_to< std::nullopt_t >(value()); BOOST_TEST_THROWS_WITH_LOCATION( value_to< std::nullopt_t >( jv, ctx... )); + + BOOST_TEST(( + value_to< std::optional >( + value("Id#1"), ctx... ) + == std::optional( + value_to_test_ns::id{1} ) )); #endif } @@ -550,6 +677,10 @@ class value_to_test using V_T3_T1 = Variant; auto v_t3_t1 = value_to( jv, ctx... ); BOOST_TEST( v_t3_t1.index() == 1 ); + + BOOST_TEST(( + value_to< Variant >( value("Id#2"), ctx... ) + == Variant( value_to_test_ns::id{2} ))); } template< class... Context > @@ -757,6 +888,13 @@ class value_to_test testUserConversion( Context const& ... ctx ) { value_to( value("T2"), ctx... ); + + auto id = value_to( + value("Id#1"), ctx... ); + BOOST_TEST( id.n == 1 ); + id = value_to( + value("Id#2"), ctx... ); + BOOST_TEST( id.n == 2 ); } void @@ -769,6 +907,19 @@ class value_to_test value_to( value(), value_to_test_ns::custom_context() ), system::system_error); + + BOOST_TEST(( + value_to< std::map >( + value{ {"1", "2"}, {"2", "4"}, {"3", "8"} }, + value_to_test_ns::as_string() ) + == std::map{ {1,2}, {2,4}, {3,8} } )); + BOOST_TEST(( + value_to< std::map >( + value{ {"1", "2"}, {"2", "4"}, {"3", "6"} }, + std::make_tuple( + value_to_test_ns::as_string(), + value_to_test_ns::custom_context() )) + == std::map{ {1,2}, {2,4}, {3,6} } )); } struct run_templated_tests From 63b26b5b7113d600b25ef52c49a7f288d2b3adc9 Mon Sep 17 00:00:00 2001 From: Dmitry Arkhipov Date: Tue, 14 Jan 2025 15:34:03 +0300 Subject: [PATCH 3/3] support for proxy types in direct parsing --- include/boost/json/detail/parse_into.hpp | 298 +++++++++++++++-------- include/boost/json/impl/conversion.hpp | 2 +- include/boost/json/impl/parse_into.hpp | 48 ++-- include/boost/json/parse_into.hpp | 34 +-- test/parse_into.cpp | 147 +++++++++-- 5 files changed, 373 insertions(+), 156 deletions(-) diff --git a/include/boost/json/detail/parse_into.hpp b/include/boost/json/detail/parse_into.hpp index c7966ebb5..7c086e1fc 100644 --- a/include/boost/json/detail/parse_into.hpp +++ b/include/boost/json/detail/parse_into.hpp @@ -31,7 +31,7 @@ * handler), and on_document_end (by disabling the nested handler). * * Every other event is handled by the nested handler, which has the type - * get_handler< T, into_handler >. The second parameter is the parent + * get_handler. The second parameter is the parent * handler (in this case, it's the top handler, into_handler). The type is * actually an alias to class template converting_handler, which has a separate * specialisation for every conversion category from the list of generic @@ -74,13 +74,13 @@ namespace boost { namespace json { namespace detail { -template< class Impl, class T, class Parent > +template< class Impl, class T, class Parent, class Ctx > class converting_handler; // get_handler -template< class V, class P > +template using get_handler = converting_handler< - generic_conversion_category, V, P>; + generic_conversion_category, V, P, Ctx>; template class handler_error_base { @@ -133,26 +133,27 @@ class scalar_handler } }; -template< class D, class V, class P, error E > +template class composite_handler { protected: - using inner_handler_type = get_handler; + using representation = conversion_representation; + using inner_handler_type = get_handler; P* parent_; #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif - V next_value_ = {}; + representation next_value_ = {}; inner_handler_type inner_; bool inner_active_ = false; public: - composite_handler( composite_handler const& ) = delete; - composite_handler& operator=( composite_handler const& ) = delete; + composite_handler(composite_handler const&) = delete; + composite_handler& operator=(composite_handler const&) = delete; - composite_handler( P* p ) + composite_handler(P* p) : parent_(p), inner_( &next_value_, static_cast(this) ) {} #if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) @@ -267,8 +268,8 @@ bool integral_in_range( std::uint64_t v ) return v <= static_cast::type>( (std::numeric_limits::max)() ); } -template< class V, class P > -class converting_handler +template< class V, class P, class Ctx > +class converting_handler : public scalar_handler { private: @@ -311,8 +312,8 @@ class converting_handler }; // floating point handler -template< class V, class P> -class converting_handler +template< class V, class P, class Ctx> +class converting_handler : public scalar_handler { private: @@ -349,8 +350,8 @@ class converting_handler }; // string handler -template< class V, class P > -class converting_handler +template +class converting_handler : public scalar_handler { private: @@ -388,8 +389,8 @@ class converting_handler }; // bool handler -template< class V, class P > -class converting_handler +template +class converting_handler : public scalar_handler { private: @@ -409,8 +410,8 @@ class converting_handler }; // null handler -template< class V, class P > -class converting_handler +template +class converting_handler : public scalar_handler { private: @@ -430,8 +431,8 @@ class converting_handler }; // described enum handler -template< class V, class P > -class converting_handler +template +class converting_handler : public scalar_handler { #ifndef BOOST_DESCRIBE_CXX14 @@ -478,8 +479,8 @@ class converting_handler #endif // BOOST_DESCRIBE_CXX14 }; -template< class V, class P > -class converting_handler +template +class converting_handler { static_assert( sizeof(V) == 0, "This type is not supported" ); }; @@ -535,12 +536,13 @@ clear_container( target.clear(); } -template< class V, class P > -class converting_handler +template +class converting_handler : public composite_handler< - converting_handler, + converting_handler, detail::value_type, P, + Ctx, error::not_array> { private: @@ -610,12 +612,13 @@ class converting_handler }; // map handler -template< class V, class P > -class converting_handler +template +class converting_handler : public composite_handler< - converting_handler, + converting_handler, detail::mapped_type, P, + Ctx, error::not_object> { private: @@ -629,9 +632,11 @@ class converting_handler bool signal_value(system::error_code&) { - value_->emplace( std::move(key_), std::move(this->next_value_) ); - - key_ = {}; + using key_rep = conversion_representation, Ctx>; + value_->emplace( + static_cast( std::move(key_) ), + std::move(this->next_value_) ); + key_.clear(); this->next_value_ = {}; this->inner_active_ = false; @@ -686,33 +691,65 @@ class converting_handler }; // tuple handler -template +template< + std::size_t I, + class P, + class Ctx, + class T, + class Rep = conversion_representation > struct handler_tuple_element { - template< class... Args > - handler_tuple_element( Args&& ... args ) - : t_( static_cast(args)... ) + using representation = conversion_representation; + using handler = get_handler; + + handler_tuple_element(T* t, P* p) + : t_(std::addressof(rep_), p), tgt_(t) {} - T t_; + void + finish() + { + *tgt_ = std::move(rep_); + } + + Rep rep_; + handler t_; + T* tgt_; }; -template -T& -get( handler_tuple_element& e ) +template +struct handler_tuple_element +{ + using handler = get_handler; + + handler_tuple_element(T* t, P* p) + : t_(t, p) + {} + + void + finish() const noexcept + {} + + handler t_; +}; + +template +typename handler_tuple_element::handler& +get( handler_tuple_element& e ) { return e.t_; } template< class P, + class Ctx, class LV, class S = mp11::make_index_sequence::value> > struct handler_tuple; -template< class P, template class L, class... V, std::size_t... I > -struct handler_tuple< P, L, mp11::index_sequence > - : handler_tuple_element +template< class P, class Ctx, template class L, class... V, std::size_t... I > +struct handler_tuple< P, Ctx, L, mp11::index_sequence > + : handler_tuple_element ... { handler_tuple( handler_tuple const& ) = delete; @@ -720,11 +757,21 @@ struct handler_tuple< P, L, mp11::index_sequence > template< class Access, class T > handler_tuple( Access access, T* pv, P* pp ) - : handler_tuple_element( + : handler_tuple_element( access( pv, mp11::mp_size_t() ), pp ) ... {} + + void + finish() + { + auto _ = { + (static_cast< handler_tuple_element* >(this) + ->finish(), 0) + ...}; + (void)_; + } }; #if defined(BOOST_MSVC) && BOOST_MSVC < 1910 @@ -799,17 +846,13 @@ struct tuple_accessor } }; -template< class T, class P > -class converting_handler +template +class converting_handler { private: using ElementTypes = tuple_element_list; - - template - using ElementHandler = get_handler; - using InnerHandlers = mp11::mp_transform; - using HandlerTuple = handler_tuple; + using HandlerTuple = handler_tuple; T* value_; P* parent_; @@ -840,6 +883,7 @@ class converting_handler return false; } + handlers_.finish(); inner_active_ = -1; return parent_->signal_value(ec); } @@ -1021,7 +1065,7 @@ struct ignoring_handler ignoring_handler(ignoring_handler const&) = delete; ignoring_handler& operator=(ignoring_handler const&) = delete; - ignoring_handler(void*, P* p) noexcept + ignoring_handler(P* p) noexcept : parent_(p) {} @@ -1120,8 +1164,8 @@ struct ignoring_handler } }; -template -class converting_handler +template +class converting_handler { #if !defined(BOOST_DESCRIBE_CXX14) @@ -1133,20 +1177,13 @@ class converting_handler private: using Dm = described_members; using Dt = struct_element_list; + using InnerCount = mp11::mp_size
; - template - using MemberHandler = get_handler; - using InnerHandlers = mp11::mp_push_back< - mp11::mp_transform, - ignoring_handler >; - using InnerCount = mp11::mp_size; - + handler_tuple handlers_; + ignoring_handler ignorer_; + std::string key_; V* value_; P* parent_; - - std::string key_; - - handler_tuple handlers_; int inner_active_ = -1; std::size_t activated_ = 0; @@ -1155,16 +1192,14 @@ class converting_handler converting_handler& operator=( converting_handler const& ) = delete; converting_handler( V* v, P* p ) - : value_(v), parent_(p), handlers_(struct_accessor(), v, this) + : handlers_(struct_accessor(), v, this) + , ignorer_(this) + , value_(v) + , parent_(p) {} struct is_required_checker { - bool operator()( mp11::mp_size
) const noexcept - { - return false; - } - template< class I > auto operator()( I ) const noexcept { @@ -1172,15 +1207,17 @@ class converting_handler return !is_optional_like::value; } }; - bool signal_value(system::error_code&) { BOOST_ASSERT( inner_active_ >= 0 ); - bool required_member = mp11::mp_with_index( - inner_active_, - is_required_checker{}); - if( required_member ) - ++activated_; + if( static_cast(inner_active_) < InnerCount::value ) + { + bool required_member = mp11::mp_with_index( + inner_active_, + is_required_checker{}); + if( required_member ) + ++activated_; + } key_ = {}; inner_active_ = -1; @@ -1200,6 +1237,8 @@ class converting_handler BOOST_JSON_FAIL( ec, error::not_object ); \ return false; \ } \ + if(inner_active_ == InnerCount::value) \ + return ignorer_.fn; \ auto f = [&](auto& handler) { return handler.fn ; }; \ using F = decltype(f); \ using H = decltype(handlers_); \ @@ -1227,6 +1266,7 @@ class converting_handler return false; } + handlers_.finish(); return parent_->signal_value(ec); } @@ -1271,7 +1311,7 @@ class converting_handler key = key_; } - inner_active_ = InnerCount::value - 1; + inner_active_ = InnerCount::value; mp11::mp_for_each( struct_key_searcher(key, inner_active_) ); return true; } @@ -1453,16 +1493,16 @@ struct event_visitor } }; -// L -> variant< monostate, get_handler... > -template< class P, class L > +// L -> variant< monostate, get_handler... > +template using inner_handler_variant = mp11::mp_push_front< mp11::mp_transform_q< - mp11::mp_bind_back, + mp11::mp_bind_back, mp11::mp_apply>, variant2::monostate>; -template< class T, class P > -class converting_handler +template +class converting_handler { private: using variant_size = mp11::mp_size; @@ -1472,7 +1512,7 @@ class converting_handler std::string string_; std::vector< parse_event > events_; - inner_handler_variant inner_; + inner_handler_variant inner_; int inner_active_ = -1; public: @@ -1658,17 +1698,19 @@ class converting_handler }; // optional handler -template -class converting_handler +template +class converting_handler { private: using inner_type = value_result_type; - using inner_handler_type = get_handler; + using inner_rep = conversion_representation; + using inner_handler_type = get_handler< + inner_rep, converting_handler, Ctx>; V* value_; P* parent_; - inner_type inner_value_ = {}; + inner_rep inner_value_ = {}; inner_handler_type inner_; bool inner_active_ = false; @@ -1783,8 +1825,8 @@ class converting_handler }; // path handler -template< class V, class P > -class converting_handler +template +class converting_handler : public scalar_handler { private: @@ -1823,36 +1865,83 @@ class converting_handler }; // into_handler -template< class V > -class into_handler +template< + class T, class Ctx, class Rep = conversion_representation > +class direct_target_holder { -private: + Rep rep_; + T* tgt_; - using inner_handler_type = get_handler; +public: + using representation = Rep; - inner_handler_type inner_; - bool inner_active_ = true; + direct_target_holder(T* tgt) + : rep_(*tgt) + , tgt_(tgt) + {} -public: + representation* + target_address(T*) noexcept + { + return std::addressof(rep_); + } - into_handler( into_handler const& ) = delete; - into_handler& operator=( into_handler const& ) = delete; + void + finish() + { + *tgt_ = std::move(rep_); + } +}; +template +class direct_target_holder +{ public: + using representation = T; + + direct_target_holder(T*) noexcept + {} + + representation* + target_address(T* tgt) const noexcept + { + return tgt; + } + + void + finish() const noexcept + {} +}; + +template +class into_handler + : direct_target_holder +{ +private: + using inner_handler_type = get_handler< + typename into_handler::representation, into_handler, Ctx>; + inner_handler_type inner_; + bool inner_active_ = true; + +public: static constexpr std::size_t max_object_size = object::max_size(); static constexpr std::size_t max_array_size = array::max_size(); static constexpr std::size_t max_key_size = string::max_size(); static constexpr std::size_t max_string_size = string::max_size(); -public: + into_handler(into_handler const&) = delete; + into_handler& operator=(into_handler const&) = delete; - explicit into_handler( V* v ): inner_( v, this ) - { - } + explicit + into_handler( V* v, Ctx const& = Ctx{} ) + : into_handler::direct_target_holder(v) + , inner_(into_handler::target_address(v), this) + {} bool signal_value(system::error_code&) { + into_handler::finish(); return true; } @@ -1960,7 +2049,6 @@ class into_handler { return true; } - #undef BOOST_JSON_INVOKE_INNER }; diff --git a/include/boost/json/impl/conversion.hpp b/include/boost/json/impl/conversion.hpp index ec1eadde4..412c45ea0 100644 --- a/include/boost/json/impl/conversion.hpp +++ b/include/boost/json/impl/conversion.hpp @@ -391,7 +391,7 @@ struct conversion_representation_impl< T, std::tuple > template< class T, class Ctx > using conversion_representation - = typename conversion_representation_impl::type; + = typename conversion_representation_impl, Ctx>::type; template< class Ctx, class T, class Dir > struct conversion_attrs diff --git a/include/boost/json/impl/parse_into.hpp b/include/boost/json/impl/parse_into.hpp index b061827fa..e7baadcc5 100644 --- a/include/boost/json/impl/parse_into.hpp +++ b/include/boost/json/impl/parse_into.hpp @@ -18,59 +18,61 @@ namespace boost { namespace json { -template +template< class V, class Ctx > void parse_into( V& v, string_view sv, system::error_code& ec, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { - parser_for p( opt, &v ); - - std::size_t n = p.write_some( false, sv.data(), sv.size(), ec ); - + parser_for p(opt, &v, ctx); + std::size_t n = p.write_some(false, sv.data(), sv.size(), ec); if( !ec && n < sv.size() ) { - BOOST_JSON_FAIL( ec, error::extra_data ); + BOOST_JSON_FAIL(ec, error::extra_data); } } -template +template< class V, class Ctx > void parse_into( V& v, string_view sv, std::error_code& ec, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { system::error_code jec; - parse_into(v, sv, jec, opt); + parse_into(v, sv, jec, opt, ctx); ec = jec; } -template +template< class V, class Ctx > void parse_into( V& v, string_view sv, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { system::error_code ec; - parse_into(v, sv, ec, opt); + parse_into(v, sv, ec, opt, ctx); if( ec.failed() ) detail::throw_system_error( ec ); } -template +template< class V, class Ctx > void parse_into( V& v, std::istream& is, system::error_code& ec, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { - parser_for p( opt, &v ); + parser_for p(opt, &v, ctx); char read_buffer[BOOST_JSON_STACK_BUFFER_SIZE]; do @@ -99,28 +101,30 @@ parse_into( while( !ec.failed() ); } -template +template< class V, class Ctx > void parse_into( V& v, std::istream& is, std::error_code& ec, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { system::error_code jec; - parse_into(v, is, jec, opt); + parse_into(v, is, jec, opt, ctx); ec = jec; } -template +template< class V, class Ctx > void parse_into( V& v, std::istream& is, - parse_options const& opt ) + parse_options const& opt, + Ctx const& ctx ) { system::error_code ec; - parse_into(v, is, ec, opt); + parse_into(v, is, ec, opt, ctx); if( ec.failed() ) detail::throw_system_error( ec ); } diff --git a/include/boost/json/parse_into.hpp b/include/boost/json/parse_into.hpp index 709f21bc9..c604307eb 100644 --- a/include/boost/json/parse_into.hpp +++ b/include/boost/json/parse_into.hpp @@ -33,10 +33,10 @@ namespace json { @tparam T the type to parse into. This type must be [*DefaultConstructible*](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible). */ -template< class T > +template< class T, class Ctx = detail::no_context > using parser_for = #ifndef BOOST_JSON_DOCS - basic_parser>; + basic_parser>; #else __see_below__; #endif @@ -70,21 +70,23 @@ using parser_for = is omitted, the parser will accept only standard JSON. */ /** @{ */ -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, string_view sv, system::error_code& ec, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, string_view sv, std::error_code& ec, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); /** @} */ /** Parse a JSON text into a user-defined object. @@ -116,12 +118,13 @@ parse_into( @throw `boost::system::system_error` on failed parse. */ -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, string_view sv, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); /** Parse a JSON text into a user-defined object. @@ -155,21 +158,23 @@ parse_into( is omitted, the parser will accept only standard JSON. */ /** @{ */ -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, std::istream& is, system::error_code& ec, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, std::istream& is, std::error_code& ec, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); /** @} */ /** Parse a JSON text into a user-defined object. @@ -203,12 +208,13 @@ parse_into( @throw `boost::system::system_error` on failed parse. */ -template +template< class V, class Ctx = detail::no_context > void parse_into( V& v, std::istream& is, - parse_options const& opt = {} ); + parse_options const& opt = {}, + Ctx const& ctx = Ctx{} ); } // namespace boost } // namespace json diff --git a/test/parse_into.cpp b/test/parse_into.cpp index 404c9d9e4..6c928cec2 100644 --- a/test/parse_into.cpp +++ b/test/parse_into.cpp @@ -75,6 +75,103 @@ struct Z : X BOOST_DEFINE_ENUM_CLASS(E, x, y, z) +struct id +{ + static constexpr char const* id1 = "Id#1"; + static constexpr char const* id2 = "Id#2"; + + std::size_t n; +}; + +bool +operator==(id l, id r) noexcept +{ + return l.n == r.n; +} + +bool +operator!=(id l, id r) noexcept +{ + return l.n != r.n; +} + +bool +operator<(id l, id r) noexcept +{ + return l.n < r.n; +} + +struct id_string_repr +{ + std::string s; + + id_string_repr() + : s(::id::id1) + {} + + id_string_repr(boost::json::string_view sv) noexcept + : s(sv) + {} + + id_string_repr(::id x) noexcept + { + switch(x.n) + { + case 1: + s = ::id::id1; + break; + case 2: + s = ::id::id2; + break; + } + } + + operator id() const + { + if( s == boost::json::string_view(id::id1, 4) ) + return {1}; + else if( s == boost::json::string_view(id::id2, 4) ) + return {2}; + throw std::runtime_error("unknown id"); + } + + operator boost::json::string_view() const noexcept + { + return s; + } + + void + clear() noexcept + { + s.clear(); + } + + void + append( char const* b, char const* e) + { + s.append(b, e); + } +}; + +struct W +{ + int n; + id z; +}; +BOOST_DESCRIBE_STRUCT(W, (), (n, z)) + +bool +operator==( W const& w1, W const& w2 ) noexcept +{ + return w1.n == w2.n && w1.z == w2.z; +} + +bool +operator!=( W const& w1, W const& w2 ) noexcept +{ + return !(w1 == w2); +} + namespace boost { namespace json { @@ -83,52 +180,59 @@ struct is_described_class : std::true_type { }; +template<> +struct represent_as<::id> +{ + using type = ::id_string_repr; +}; + class parse_into_test { public: - template - void testParseIntoValue( value const& jv ) + template + void testParseIntoValue( value const& jv, Ctx const& ctx = Ctx{} ) { + parse_options opts; #if defined(__GNUC__) && __GNUC__ < 5 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif - T t1 = value_to(jv); + T t1 = value_to(jv, ctx); (void)t1; // older GCC thinks t1 can be unused std::string json = serialize(jv); T t2{}; system::error_code jec; - parse_into(t2, json, jec); + parse_into(t2, json, jec, opts, ctx); BOOST_TEST( !jec.failed() ) && BOOST_TEST( t1 == t2 ); T t3{}; std::error_code ec; - parse_into(t3, json, ec); + parse_into(t3, json, ec, opts, ctx); BOOST_TEST( !ec ) && BOOST_TEST( t1 == t3 ); T t4{}; - parse_into(t4, json); + parse_into(t4, json, opts, ctx); BOOST_TEST( t1 == t4 ); std::istringstream is(json); T t5{}; jec = {}; - parse_into(t5, is, jec); + parse_into(t5, is, jec, opts, ctx); BOOST_TEST( !jec.failed() ) && BOOST_TEST( t1 == t5 ); is.clear(); is.seekg(0); T t6{}; ec = {}; - parse_into(t6, is, ec); + parse_into(t6, is, ec, opts, ctx); BOOST_TEST( !ec ) && BOOST_TEST( t1 == t6 ); is.str(json); is.clear(); T t7{}; - parse_into(t7, is); + parse_into(t7, is, opts, ctx); BOOST_TEST( t1 == t7 ); parse_options opt; @@ -136,8 +240,8 @@ class parse_into_test json = "// this is a comment\n" + json; T t8{}; - parser_for p( opt, &t8 ); - for( auto& c: json ) + parser_for p(opt, &t8, ctx); + for(auto& c: json) { std::size_t const n = p.write_some( true, &c, 1, jec ); BOOST_TEST( !jec.failed() ); @@ -151,10 +255,10 @@ class parse_into_test #endif } - template - void testParseInto( T const& t ) + template + void testParseInto( T const& t, Ctx const& ctx = Ctx{} ) { - testParseIntoValue( value_from(t) ); + testParseIntoValue(value_from(t, ctx), ctx); } template @@ -226,6 +330,8 @@ class parse_into_test testParseIntoErrors< int >( error::not_integer, true ); testParseIntoErrors< int >( error::not_exact, LLONG_MIN ); testParseIntoErrors< int >( error::not_exact, ULONG_MAX ); + + testParseInto( ::id{1} ); } void testFloatingPoint() @@ -314,6 +420,8 @@ class parse_into_test parse_into(v, "[5,6,7]"); BOOST_TEST( v.size() == 3 ); + + testParseInto< std::vector<::id> >( { {1}, {2}, {1}, {2} } ); } void testMap() @@ -341,6 +449,10 @@ class parse_into_test parse_into(m, R"( {"4": 4, "5": 5} )"); BOOST_TEST( m.size() == 2 ); + + testParseInto< std::map >({ + {"1", {1}}, {"2", {2}}, {"3", {1}}, {"4", {2}} }); + testParseInto< std::map<::id, ::id> >({ {{2}, {1}}, {{1}, {2}} }); } void testTuple() @@ -374,6 +486,8 @@ class parse_into_test error::size_mismatch, {{1,2}, {3,4}} ); testParseIntoErrors>>( error::size_mismatch, { {"tup", array()} }); + + testParseInto< std::tuple >( std::make_tuple(10, ::id{2}) ); } void testStruct() @@ -401,6 +515,8 @@ class parse_into_test jo["e7"] = false; jo["e8"] = nullptr; testParseIntoValue(jo); + + testParseInto( {10, ::id{2}} ); #endif } @@ -478,6 +594,9 @@ class parse_into_test testParseInto< std::vector< std::optional > >( {1, 2, 3, std::nullopt, 5, std::nullopt, std::nullopt, 8}); + + testParseInto< std::optional<::id> >( std::nullopt ); + testParseInto< std::optional<::id> >( ::id{2} ); #endif }