// // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2020 Krystian Stasiowski (sdkrystian@gmail.com) // Copyright (c) 2021 Dmitry Arkhipov (grisumbras@gmail.com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Official repository: https://github.com/boostorg/json // #ifndef BOOST_JSON_DETAIL_VALUE_TO_HPP #define BOOST_JSON_DETAIL_VALUE_TO_HPP #include #include #include #include #ifndef BOOST_NO_CXX17_HDR_OPTIONAL # include #endif namespace boost { namespace json { namespace detail { template using has_reserve_member_helper = decltype(std::declval().reserve(0)); template using has_reserve_member = mp11::mp_valid; template using reserve_implementation = mp11::mp_cond< is_tuple_like, mp11::mp_int<2>, has_reserve_member, mp11::mp_int<1>, mp11::mp_true, mp11::mp_int<0>>; template error try_reserve( T&, std::size_t size, mp11::mp_int<2>) { constexpr std::size_t N = std::tuple_size>::value; if ( N != size ) return error::size_mismatch; return error(); } template error try_reserve( T& cont, std::size_t size, mp11::mp_int<1>) { cont.reserve(size); return error(); } template error try_reserve( T&, std::size_t, mp11::mp_int<0>) { return error(); } // identity conversion template< class Ctx > system::result value_to_impl( value_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { return jv; } template< class Ctx > value value_to_impl( value_conversion_tag, value_to_tag, value const& jv, Ctx const& ) { return jv; } // object template< class Ctx > system::result value_to_impl( object_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { object const* obj = jv.if_object(); if( obj ) return *obj; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_object); return ec; } // array template< class Ctx > system::result value_to_impl( array_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { array const* arr = jv.if_array(); if( arr ) return *arr; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_array); return ec; } // string template< class Ctx > system::result value_to_impl( string_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { string const* str = jv.if_string(); if( str ) return *str; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_string); return ec; } // bool template< class Ctx > system::result value_to_impl( bool_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { auto b = jv.if_bool(); if( b ) return *b; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_bool); return {boost::system::in_place_error, ec}; } // integral and floating point template< class T, class Ctx > system::result value_to_impl( number_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { system::error_code ec; auto const n = jv.to_number(ec); if( ec.failed() ) return {boost::system::in_place_error, ec}; return {boost::system::in_place_value, n}; } // null-like conversion template< class T, class Ctx > system::result value_to_impl( null_like_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { if( jv.is_null() ) return {boost::system::in_place_value, T{}}; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_null); return {boost::system::in_place_error, ec}; } // string-like types template< class T, class Ctx > system::result value_to_impl( string_like_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { auto str = jv.if_string(); if( str ) return {boost::system::in_place_value, T(str->subview())}; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_string); return {boost::system::in_place_error, ec}; } // map-like containers template< class T, class Ctx > system::result value_to_impl( map_like_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { object const* obj = jv.if_object(); if( !obj ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::not_object); return {boost::system::in_place_error, ec}; } T res; error const e = detail::try_reserve( res, obj->size(), reserve_implementation()); if( e != error() ) { system::error_code ec; BOOST_JSON_FAIL( ec, e ); return {boost::system::in_place_error, ec}; } auto ins = detail::inserter(res, inserter_implementation()); for( key_value_pair const& kv: *obj ) { 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()), std::move(*elem_res)}; } return res; } // all other containers template< class T, class Ctx > system::result value_to_impl( sequence_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { array const* arr = jv.if_array(); if( !arr ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::not_array); return {boost::system::in_place_error, ec}; } T result; error const e = detail::try_reserve( result, arr->size(), reserve_implementation()); if( e != error() ) { system::error_code ec; BOOST_JSON_FAIL( ec, e ); return {boost::system::in_place_error, ec}; } auto ins = detail::inserter(result, inserter_implementation()); for( value const& val: *arr ) { auto elem_res = try_value_to>( val, ctx ); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = std::move(*elem_res); } return result; } // tuple-like types template< class T, class Ctx > system::result try_make_tuple_elem(value const& jv, Ctx const& ctx, system::error_code& ec) { if( ec.failed() ) return {boost::system::in_place_error, ec}; auto result = try_value_to( jv, ctx ); ec = result.error(); return result; } template system::result try_make_tuple_like( array const& arr, Ctx const& ctx, boost::mp11::index_sequence) { system::error_code ec; auto items = std::make_tuple( try_make_tuple_elem< typename std::decay>::type >( arr[Is], ctx, ec) ...); if( ec.failed() ) return {boost::system::in_place_error, ec}; return { boost::system::in_place_value, T(std::move(*std::get(items))...)}; } template< class T, class Ctx > system::result value_to_impl( tuple_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { system::error_code ec; array const* arr = jv.if_array(); if( !arr ) { BOOST_JSON_FAIL(ec, error::not_array); return {boost::system::in_place_error, ec}; } constexpr std::size_t N = std::tuple_size>::value; if( N != arr->size() ) { BOOST_JSON_FAIL(ec, error::size_mismatch); return {boost::system::in_place_error, ec}; } return try_make_tuple_like( *arr, ctx, boost::mp11::make_index_sequence()); } template< class Ctx, class T, bool non_throwing = true > struct to_described_member { using Ds = described_members; using result_type = mp11::mp_eval_if_c< !non_throwing, T, system::result, T>; result_type& res; object const& obj; std::size_t count; Ctx const& ctx; template< class I > void operator()(I) { if( !res ) return; using D = mp11::mp_at; using M = described_member_t; auto const found = obj.find(D::name); if( found == obj.end() ) { BOOST_IF_CONSTEXPR( !is_optional_like::value ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::unknown_name); res = {boost::system::in_place_error, ec}; } return; } #if defined(__GNUC__) && BOOST_GCC_VERSION >= 80000 && BOOST_GCC_VERSION < 11000 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused" # pragma GCC diagnostic ignored "-Wunused-variable" #endif auto member_res = try_value_to( found->value(), ctx ); #if defined(__GNUC__) && BOOST_GCC_VERSION >= 80000 && BOOST_GCC_VERSION < 11000 # pragma GCC diagnostic pop #endif if( member_res ) { (*res).* D::pointer = std::move(*member_res); ++count; } else res = {boost::system::in_place_error, member_res.error()}; } }; // described classes template< class T, class Ctx > system::result value_to_impl( described_class_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { BOOST_STATIC_ASSERT( std::is_default_constructible::value ); system::result res; auto* obj = jv.if_object(); if( !obj ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::not_object); res = {boost::system::in_place_error, ec}; return res; } to_described_member< Ctx, T > member_converter{ res, *obj, 0u, ctx }; using Ds = typename decltype(member_converter)::Ds; constexpr std::size_t N = mp11::mp_size::value; mp11::mp_for_each< mp11::mp_iota_c >(member_converter); if( !res ) return res; if( member_converter.count != obj->size() ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::size_mismatch); res = {boost::system::in_place_error, ec}; return res; } return res; } // described enums template< class T, class Ctx > system::result value_to_impl( described_enum_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { T val = {}; (void)jv; #ifdef BOOST_DESCRIBE_CXX14 system::error_code ec; auto str = jv.if_string(); if( !str ) { BOOST_JSON_FAIL(ec, error::not_string); return {system::in_place_error, ec}; } if( !describe::enum_from_string(str->data(), val) ) { BOOST_JSON_FAIL(ec, error::unknown_name); return {system::in_place_error, ec}; } #endif return {system::in_place_value, val}; } // optionals template< class T, class Ctx > system::result value_to_impl( optional_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx) { using Inner = value_result_type; if( jv.is_null() ) return {}; else return try_value_to(jv, ctx); } // variants template< class T, class V, class I > using variant_construction_category = mp11::mp_cond< std::is_constructible< T, variant2::in_place_index_t, V >, mp11::mp_int<2>, #ifndef BOOST_NO_CXX17_HDR_VARIANT std::is_constructible< T, std::in_place_index_t, V >, mp11::mp_int<1>, #endif // BOOST_NO_CXX17_HDR_VARIANT mp11::mp_true, mp11::mp_int<0> >; template< class T, class I, class V > T initialize_variant( V&& v, mp11::mp_int<0> ) { T t; t.template emplace( std::move(v) ); return t; } template< class T, class I, class V > T initialize_variant( V&& v, mp11::mp_int<2> ) { return T( variant2::in_place_index_t(), std::move(v) ); } #ifndef BOOST_NO_CXX17_HDR_VARIANT template< class T, class I, class V > T initialize_variant( V&& v, mp11::mp_int<1> ) { return T( std::in_place_index_t(), std::move(v) ); } #endif // BOOST_NO_CXX17_HDR_VARIANT struct locally_prohibit_exceptions {}; template< class Ctx > Ctx const& make_locally_nonthrowing_context(Ctx const& ctx) noexcept { return ctx; } template< class... Ctxes > std::tuple const& make_locally_nonthrowing_context(std::tuple const& ctx) noexcept { return ctx; } template< class... Ctxes > std::tuple make_locally_nonthrowing_context(std::tuple const& ctx) noexcept { return std::tuple_cat(std::make_tuple( locally_prohibit_exceptions() ), ctx); } template< class Ctx > Ctx const& remove_local_exception_prohibition(Ctx const& ctx) noexcept { return ctx; } template< class T, class... Ts, std::size_t... Is> std::tuple remove_local_exception_prohibition_helper( std::tuple const& tup, mp11::index_sequence) noexcept { return std::tuple( std::get(tup)... ); } template< class... Ctxes > std::tuple remove_local_exception_prohibition( std::tuple const& ctx) noexcept { return remove_local_exception_prohibition_helper( ctx, mp11::index_sequence_for() ); } template< class T, class Ctx > struct alternative_converter { system::result& res; value const& jv; Ctx const& ctx; template< class I > void operator()( I ) const { if( res ) return; auto&& local_ctx = make_locally_nonthrowing_context(ctx); using V = mp11::mp_at; auto attempt = try_value_to(jv, local_ctx); if( attempt ) { using cat = variant_construction_category; res = initialize_variant( std::move(*attempt), cat() ); } } }; template< class T, class Ctx > system::result value_to_impl( variant_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx) { system::error_code ec; BOOST_JSON_FAIL(ec, error::exhausted_variants); using Is = mp11::mp_iota< mp11::mp_size >; system::result res = {system::in_place_error, ec}; mp11::mp_for_each( alternative_converter{res, jv, ctx} ); return res; } template< class T, class Ctx > system::result value_to_impl( path_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { auto str = jv.if_string(); if( !str ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::not_string); return {boost::system::in_place_error, ec}; } string_view sv = str->subview(); return {boost::system::in_place_value, T( sv.begin(), sv.end() )}; } //---------------------------------------------------------- // User-provided conversions; throwing -> throwing template< class T, class Ctx > mp11::mp_if< mp11::mp_valid, T > value_to_impl( user_conversion_tag, value_to_tag tag, value const& jv, Ctx const&) { return tag_invoke(tag, jv); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if< mp11::mp_valid< has_context_conversion_to_impl, typename Sup::type, T>, T > value_to_impl( context_conversion_tag, value_to_tag tag, value const& jv, Ctx const& ctx ) { return tag_invoke( tag, jv, Sup::get(ctx) ); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if< mp11::mp_valid< has_full_context_conversion_to_impl, typename Sup::type, T>, T> value_to_impl( full_context_conversion_tag, value_to_tag tag, value const& jv, Ctx const& ctx ) { return tag_invoke( tag, jv, Sup::get(ctx), ctx ); } //---------------------------------------------------------- // User-provided conversions; throwing -> nonthrowing template< class T, class Ctx > mp11::mp_if_c< !mp11::mp_valid::value, T> value_to_impl( user_conversion_tag, value_to_tag, value const& jv, Ctx const& ) { auto res = tag_invoke(try_value_to_tag(), jv); if( res.has_error() ) throw_system_error( res.error() ); return std::move(*res); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if_c< !mp11::mp_valid< has_context_conversion_to_impl, typename Sup::type, T>::value, T> value_to_impl( context_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) { auto res = tag_invoke( try_value_to_tag(), jv, Sup::get(ctx) ); if( res.has_error() ) throw_system_error( res.error() ); return std::move(*res); } template< class Ctx > std::tuple make_throwing_context(Ctx const& ctx) { return std::tuple(allow_exceptions(), ctx); } template< class... Ctxes > std::tuple make_throwing_context(std::tuple const& ctx) { return std::tuple_cat(std::make_tuple( allow_exceptions() ), ctx); } template< class... Ctxes > std::tuple const& make_throwing_context(std::tuple const& ctx) noexcept { return ctx; } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if_c< !mp11::mp_valid< has_full_context_conversion_to_impl, typename Sup::type, T>::value, T> value_to_impl( full_context_conversion_tag, value_to_tag, value const& jv, Ctx const& ctx ) { auto res = tag_invoke( try_value_to_tag(), jv, Sup::get(ctx), make_throwing_context(ctx)); if( res.has_error() ) throw_system_error( res.error() ); return std::move(*res); } //---------------------------------------------------------- // User-provided conversions; nonthrowing -> nonthrowing template< class T, class Ctx > mp11::mp_if< mp11::mp_valid< has_nonthrowing_user_conversion_to_impl, T>, system::result > value_to_impl( user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { return tag_invoke(try_value_to_tag(), jv); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if< mp11::mp_valid< has_nonthrowing_context_conversion_to_impl, typename Sup::type, T>, system::result > value_to_impl( context_conversion_tag, try_value_to_tag tag, value const& jv, Ctx const& ctx ) { return tag_invoke( tag, jv, Sup::get(ctx) ); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if< mp11::mp_valid< has_nonthrowing_full_context_conversion_to_impl, typename Sup::type, T>, system::result > value_to_impl( full_context_conversion_tag, try_value_to_tag tag, value const& jv, Ctx const& ctx ) { return tag_invoke( tag, jv, Sup::get(ctx), ctx ); } //---------------------------------------------------------- // User-provided conversions; nonthrowing -> throwing template< class Ctx > struct does_allow_exceptions : std::false_type { }; template< class... Ctxes > struct does_allow_exceptions< std::tuple > : std::true_type { }; template< class T, class... Args > system::result wrap_conversion_exceptions( std::true_type, value_to_tag, Args&& ... args ) { return { boost::system::in_place_value, tag_invoke( value_to_tag(), static_cast(args)... )}; } template< class T, class... Args > system::result wrap_conversion_exceptions( std::false_type, value_to_tag, Args&& ... args ) { #ifndef BOOST_NO_EXCEPTIONS try { #endif return wrap_conversion_exceptions( std::true_type(), value_to_tag(), static_cast(args)... ); #ifndef BOOST_NO_EXCEPTIONS } catch( std::bad_alloc const&) { throw; } catch( system::system_error const& e) { return {boost::system::in_place_error, e.code()}; } catch( ... ) { system::error_code ec; BOOST_JSON_FAIL(ec, error::exception); return {boost::system::in_place_error, ec}; } #endif } template< class T, class Ctx > mp11::mp_if_c< !mp11::mp_valid::value, system::result > value_to_impl( user_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ) { return wrap_conversion_exceptions( does_allow_exceptions(), value_to_tag(), jv); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if_c< !mp11::mp_valid< has_nonthrowing_context_conversion_to_impl, typename Sup::type, T>::value, system::result > value_to_impl( context_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { return wrap_conversion_exceptions( does_allow_exceptions(), value_to_tag(), jv, Sup::get(ctx) ); } template< class T, class Ctx, class Sup = supported_context > mp11::mp_if_c< !mp11::mp_valid< has_nonthrowing_full_context_conversion_to_impl, typename Sup::type, T>::value, system::result > value_to_impl( full_context_conversion_tag, try_value_to_tag, value const& jv, Ctx const& ctx ) { return wrap_conversion_exceptions( does_allow_exceptions(), value_to_tag(), jv, Sup::get(ctx), remove_local_exception_prohibition(ctx) ); } // no suitable conversion implementation template< class T, class Ctx > T value_to_impl( no_conversion_tag, value_to_tag, value const&, Ctx const& ) { static_assert( !std::is_same::value, "No suitable tag_invoke overload found for the type"); } // generic wrapper over non-throwing implementations template< class Impl, class T, class Ctx > T value_to_impl( Impl impl, value_to_tag, value const& jv, Ctx const& ctx ) { return value_to_impl( impl, try_value_to_tag(), jv, make_throwing_context(ctx) ).value(); } template< class Ctx, class T > using value_to_category = conversion_category< Ctx, T, value_to_conversion >; } // detail #ifndef BOOST_NO_CXX17_HDR_OPTIONAL inline system::result tag_invoke( try_value_to_tag, value const& jv) { if( jv.is_null() ) return std::nullopt; system::error_code ec; BOOST_JSON_FAIL(ec, error::not_null); return ec; } #endif } // namespace json } // namespace boost #endif