Skip to content

Commit

Permalink
(try_)value_to supports conversion through helper types
Browse files Browse the repository at this point in the history
  • Loading branch information
grisumbras committed Jan 13, 2025
1 parent abf9f8f commit 9f3a82a
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 14 deletions.
12 changes: 7 additions & 5 deletions include/boost/json/detail/value_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ namespace json {

namespace detail {

template< class Ctx, class T >
using value_to_attrs = conversion_attrs<
Ctx, remove_cvref<T>, value_to_conversion>;

template<class T>
using has_reserve_member_helper = decltype(std::declval<T&>().reserve(0));
template<class T>
Expand Down Expand Up @@ -232,11 +236,13 @@ value_to_impl(
auto ins = detail::inserter(res, inserter_implementation<T>());
for( key_value_pair const& kv: *obj )
{
using A = value_to_attrs< Ctx, key_type<T> >;
using K = typename A::representation;
auto elem_res = try_value_to<mapped_type<T>>( kv.value(), ctx );
if( elem_res.has_error() )
return {boost::system::in_place_error, elem_res.error()};
*ins++ = value_type<T>{
key_type<T>(kv.key()),
K(kv.key()),
std::move(*elem_res)};
}
return res;
Expand Down Expand Up @@ -817,10 +823,6 @@ value_to_impl( Impl impl, value_to_tag<T>, value const& jv, Ctx const& ctx )
return value_to_impl(impl, try_value_to_tag<T>(), jv, ctx).value();
}

template< class Ctx, class T >
using value_to_attrs = conversion_attrs<
Ctx, remove_cvref<T>, value_to_conversion>;

} // detail

#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
Expand Down
17 changes: 8 additions & 9 deletions include/boost/json/value_to.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,13 @@ value_to( value const& jv, Context const& ctx )
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);

using Attrs = detail::value_to_attrs<Context, T>;
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<T>;
return detail::value_to_impl(
typename Attrs::category(), value_to_tag<bare_T>(), jv, ctx);
return static_cast<bare_T>(detail::value_to_impl(
typename Attrs::category(), value_to_tag<Rep>(), jv, ctx ));
}

/** Convert a @ref value to an object of type `T`.
Expand Down Expand Up @@ -230,14 +229,14 @@ try_value_to( value const& jv, Context const& ctx )
BOOST_STATIC_ASSERT(! std::is_reference<T>::value);

using Attrs = detail::value_to_attrs<Context, T>;
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<T>;
return detail::value_to_impl(
typename Attrs::category(), try_value_to_tag<bare_T>(), jv, ctx );
typename Attrs::category(), try_value_to_tag<Rep>(), jv, ctx )
& [](Rep&& rep) { return static_cast<bare_T>(rep); };
}

/** Convert a @ref value to a `boost::system::result<T>`.
Expand Down
150 changes: 150 additions & 0 deletions test/value_to.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,97 @@ tag_invoke(
return T11( jv.to_number<int>() );
}

struct id
{
static constexpr auto& id1 = "Id#1";
static constexpr auto& 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( std::equal( sv.begin(), sv.end(), id::id1) )
n = 1;
else if( std::equal( sv.begin(), sv.end(), id::id2) )
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);
case 2: return boost::json::string_view(id::id2);
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
Expand Down Expand Up @@ -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<int, value_to_test_ns::as_string>
{
using type = ::value_to_test_ns::int_as_string;
};

template <class T, class = void>
struct can_apply_value_to
: std::false_type
Expand Down Expand Up @@ -335,6 +438,19 @@ class value_to_test
BOOST_TEST_CONV( arr, ctx... );
}

BOOST_TEST((
value_to< std::vector<value_to_test_ns::id> >(
value{"Id#1", "Id#2", "Id#2", "Id#1"}, ctx... )
== std::vector<value_to_test_ns::id>{{1}, {2}, {2}, {1}} ));
BOOST_TEST((
value_to< std::tuple<value_to_test_ns::id, int> >(
value{"Id#1", 12}, ctx... )
== std::tuple<value_to_test_ns::id, int>{{1}, 12} ));
BOOST_TEST((
value_to< std::map<value_to_test_ns::id, int> >(
value{ {"Id#1", 42}, {"Id#2", 43} }, ctx... )
== std::map<value_to_test_ns::id, int>{ {{1}, 42}, {{2}, 43} } ));

// mismatched type
BOOST_TEST_THROWS_WITH_LOCATION(
value_to<std::string>( value(), ctx... ));
Expand Down Expand Up @@ -464,6 +580,11 @@ class value_to_test
BOOST_TEST( e1 == ::value_to_test_ns::E1::b );
}

BOOST_TEST((
value_to<value_to_test_ns::T12>(
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(
Expand Down Expand Up @@ -506,6 +627,11 @@ 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_to_test_ns::id> >(
value("Id#1"), ctx... )
== std::optional<value_to_test_ns::id>( {1} ) ));
#endif
}

Expand Down Expand Up @@ -550,6 +676,10 @@ class value_to_test
using V_T3_T1 = Variant<value_to_test_ns::T3, value_to_test_ns::T1>;
auto v_t3_t1 = value_to<V_T3_T1>( jv, ctx... );
BOOST_TEST( v_t3_t1.index() == 1 );

BOOST_TEST((
value_to< Variant<value_to_test_ns::id> >( value("Id#2"), ctx... )
== Variant<value_to_test_ns::id>( value_to_test_ns::id{2} )));
}

template< class... Context >
Expand Down Expand Up @@ -757,6 +887,13 @@ class value_to_test
testUserConversion( Context const& ... ctx )
{
value_to<value_to_test_ns::T2>( value("T2"), ctx... );

auto id = value_to<value_to_test_ns::id>(
value("Id#1"), ctx... );
BOOST_TEST( id.n == 1 );
id = value_to<value_to_test_ns::id>(
value("Id#2"), ctx... );
BOOST_TEST( id.n == 2 );
}

void
Expand All @@ -769,6 +906,19 @@ class value_to_test
value_to<value_to_test_ns::T9>(
value(), value_to_test_ns::custom_context() ),
system::system_error);

BOOST_TEST((
value_to< std::map<int, int> >(
value{ {"1", "2"}, {"2", "4"}, {"3", "8"} },
value_to_test_ns::as_string() )
== std::map<int, int>{ {1,2}, {2,4}, {3,8} } ));
BOOST_TEST((
value_to< std::map<int, int> >(
value{ {"1", "2"}, {"2", "4"}, {"3", "6"} },
std::make_tuple(
value_to_test_ns::as_string(),
value_to_test_ns::custom_context() ))
== std::map<int, int>{ {1,2}, {2,4}, {3,6} } ));
}

struct run_templated_tests
Expand Down

0 comments on commit 9f3a82a

Please sign in to comment.