// // 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 #ifndef BOOST_NO_CXX17_HDR_OPTIONAL # include #endif BOOST_JSON_NS_BEGIN template::value && std::is_same::value>::type> T value_to(U const&); template typename result_for::type try_value_to(const value& jv); 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_code try_reserve( T&, std::size_t size, mp11::mp_int<2>) { error_code ec; constexpr std::size_t N = std::tuple_size>::value; if ( N != size ) { BOOST_JSON_FAIL(ec, error::size_mismatch); } return ec; } template error_code try_reserve( T& cont, std::size_t size, mp11::mp_int<1>) { cont.reserve(size); return error_code(); } template error_code try_reserve( T&, std::size_t, mp11::mp_int<0>) { return error_code(); } template using has_push_back_helper = decltype(std::declval().push_back(std::declval>())); template using has_push_back = mp11::mp_valid; template using inserter_implementation = mp11::mp_cond< is_tuple_like, mp11::mp_int<2>, has_push_back, mp11::mp_int<1>, mp11::mp_true, mp11::mp_int<0>>; template iterator_type inserter( T& target, mp11::mp_int<2>) { return target.begin(); } template std::back_insert_iterator inserter( T& target, mp11::mp_int<1>) { return std::back_inserter(target); } template std::insert_iterator inserter( T& target, mp11::mp_int<0>) { return std::inserter(target, end(target)); } // identity conversion inline result value_to_impl( try_value_to_tag, value const& jv, value_conversion_tag) { return jv; } inline value value_to_impl( value_to_tag, value const& jv, value_conversion_tag) { return jv; } // object inline result value_to_impl( try_value_to_tag, value const& jv, object_conversion_tag) { object const* obj = jv.if_object(); if( obj ) return *obj; error_code ec; BOOST_JSON_FAIL(ec, error::not_object); return ec; } // array inline result value_to_impl( try_value_to_tag, value const& jv, array_conversion_tag) { array const* arr = jv.if_array(); if( arr ) return *arr; error_code ec; BOOST_JSON_FAIL(ec, error::not_array); return ec; } // string inline result value_to_impl( try_value_to_tag, value const& jv, string_conversion_tag) { string const* str = jv.if_string(); if( str ) return *str; error_code ec; BOOST_JSON_FAIL(ec, error::not_string); return ec; } // bool inline result value_to_impl( try_value_to_tag, value const& jv, bool_conversion_tag) { auto b = jv.if_bool(); if( b ) return *b; error_code ec; BOOST_JSON_FAIL(ec, error::not_bool); return {boost::system::in_place_error, ec}; } // integral and floating point template result value_to_impl( try_value_to_tag, value const& jv, number_conversion_tag) { 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 result value_to_impl( try_value_to_tag, value const& jv, null_like_conversion_tag) { if( jv.is_null() ) return {boost::system::in_place_value, T{}}; error_code ec; BOOST_JSON_FAIL(ec, error::not_null); return {boost::system::in_place_error, ec}; } // string-like types template result value_to_impl( try_value_to_tag, value const& jv, string_like_conversion_tag) { auto str = jv.if_string(); if( str ) return {boost::system::in_place_value, T(str->subview())}; error_code ec; BOOST_JSON_FAIL(ec, error::not_string); return {boost::system::in_place_error, ec}; } // map-like containers template result value_to_impl( try_value_to_tag, value const& jv, map_like_conversion_tag) { error_code ec; object const* obj = jv.if_object(); if( !obj ) { BOOST_JSON_FAIL(ec, error::not_object); return {boost::system::in_place_error, ec}; } T res; ec = detail::try_reserve(res, obj->size(), reserve_implementation()); if( ec.failed() ) 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()); 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; } template T value_to_impl( value_to_tag, value const& jv, map_like_conversion_tag) { error_code ec; object const* obj = jv.if_object(); if( !obj ) { BOOST_JSON_FAIL(ec, error::not_object); throw_system_error(ec, BOOST_CURRENT_LOCATION); } T result; ec = detail::try_reserve(result, obj->size(), reserve_implementation()); if( ec.failed() ) throw_system_error(ec, BOOST_CURRENT_LOCATION); auto ins = detail::inserter(result, inserter_implementation()); for( key_value_pair const& kv: *obj ) *ins++ = value_type{ key_type(kv.key()), value_to>(kv.value())}; return result; } // all other containers template result value_to_impl( try_value_to_tag, value const& jv, sequence_conversion_tag) { 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}; } T result; ec = detail::try_reserve(result, arr->size(), reserve_implementation()); if( ec.failed() ) 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); if( elem_res.has_error() ) return {boost::system::in_place_error, elem_res.error()}; *ins++ = std::move(*elem_res); } return result; } template T value_to_impl( value_to_tag, value const& jv, sequence_conversion_tag) { error_code ec; array const* arr = jv.if_array(); if( !arr ) { BOOST_JSON_FAIL(ec, error::not_array); throw_system_error(ec, BOOST_CURRENT_LOCATION); } T result; ec = detail::try_reserve(result, arr->size(), reserve_implementation()); if( ec.failed() ) throw_system_error(ec, BOOST_CURRENT_LOCATION); auto ins = detail::inserter(result, inserter_implementation()); for( value const& val: *arr ) *ins++ = value_to>(val); return result; } // tuple-like types template result try_make_tuple_elem(value const& jv, error_code& ec) { if( ec.failed() ) return {boost::system::in_place_error, ec}; auto result = try_value_to(jv); ec = result.error(); return result; } template result try_make_tuple_like(array const& arr, boost::mp11::index_sequence) { error_code ec; auto items = std::make_tuple( try_make_tuple_elem>( arr[Is], ec) ...); if( ec.failed() ) return {boost::system::in_place_error, ec}; return { boost::system::in_place_value, T(std::move(*std::get(items))...)}; } template T make_tuple_like(array const& arr, boost::mp11::index_sequence) { return T(value_to>(arr[Is])...); } template result value_to_impl( try_value_to_tag, value const& jv, tuple_conversion_tag) { 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, boost::mp11::make_index_sequence()); } template T value_to_impl( value_to_tag, value const& jv, tuple_conversion_tag) { error_code ec; array const* arr = jv.if_array(); if( !arr ) { BOOST_JSON_FAIL(ec, error::not_array); throw_system_error(ec, BOOST_CURRENT_LOCATION); } constexpr std::size_t N = std::tuple_size>::value; if( N != arr->size() ) { BOOST_JSON_FAIL(ec, error::size_mismatch); throw_system_error(ec, BOOST_CURRENT_LOCATION); } return make_tuple_like( *arr, boost::mp11::make_index_sequence()); } template< class T > struct to_described_member { using Ds = describe::describe_members< T, describe::mod_public | describe::mod_inherited>; template< class D > using described_member_t = remove_cvref().* D::pointer )>; result& res; object const& obj; template< class I > void operator()(I) const { if( !res ) return; using D = mp11::mp_at; auto const found = obj.find(D::name); if( found == obj.end() ) { error_code ec; BOOST_JSON_FAIL(ec, error::unknown_name); res = {boost::system::in_place_error, ec}; return; } using M = described_member_t; auto member_res = try_value_to(found->value()); if( member_res ) (*res).* D::pointer = std::move(*member_res); else res = {boost::system::in_place_error, member_res.error()}; } }; // described classes template result value_to_impl( try_value_to_tag, value const& jv, described_class_conversion_tag) { result res; auto* obj = jv.if_object(); if( !obj ) { error_code ec; BOOST_JSON_FAIL(ec, error::not_object); res = {boost::system::in_place_error, ec}; return res; } to_described_member member_converter{res, *obj}; using Ds = typename decltype(member_converter)::Ds; constexpr std::size_t N = mp11::mp_size::value; if( obj->size() != N ) { error_code ec; BOOST_JSON_FAIL(ec, error::size_mismatch); res = {boost::system::in_place_error, ec}; return res; } mp11::mp_for_each< mp11::mp_iota_c >(member_converter); return res; } // described enums template result value_to_impl( try_value_to_tag, value const& jv, described_enum_conversion_tag) { T val = {}; (void)jv; #ifdef BOOST_DESCRIBE_CXX14 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}; } //---------------------------------------------------------- // User-provided conversion template typename std::enable_if< mp11::mp_valid::value, T>::type value_to_impl( value_to_tag tag, value const& jv, user_conversion_tag) { return tag_invoke(tag, jv); } template typename std::enable_if< !mp11::mp_valid::value, T>::type value_to_impl( value_to_tag, value const& jv, user_conversion_tag) { auto res = tag_invoke(try_value_to_tag(), jv); if( res.has_error() ) throw_system_error(res.error(), BOOST_CURRENT_LOCATION); return std::move(*res); } template typename std::enable_if< mp11::mp_valid::value, result>::type value_to_impl( try_value_to_tag, value const& jv, user_conversion_tag) { return tag_invoke(try_value_to_tag(), jv); } template typename std::enable_if< !mp11::mp_valid::value, result>::type value_to_impl( try_value_to_tag, value const& jv, user_conversion_tag) { try { return { boost::system::in_place_value, tag_invoke(value_to_tag(), jv)}; } catch( std::bad_alloc const&) { throw; } catch( system_error const& e) { return {boost::system::in_place_error, e.code()}; } catch( ... ) { error_code ec; BOOST_JSON_FAIL(ec, error::exception); return {boost::system::in_place_error, ec}; } } // no suitable conversion implementation template T value_to_impl( value_to_tag, value const&, no_conversion_tag) { static_assert( !std::is_same::value, "No suitable tag_invoke overload found for the type"); } // generic wrapper over non-throwing implementations template T value_to_impl( value_to_tag, value const& jv, Impl impl) { return value_to_impl(try_value_to_tag(), jv, impl).value(); } template using value_to_implementation = conversion_implementation; } // detail // std::optional #ifndef BOOST_NO_CXX17_HDR_OPTIONAL template result> tag_invoke( try_value_to_tag>, value const& jv) { if( jv.is_null() ) return std::optional(); else return try_value_to(jv); } inline result tag_invoke( try_value_to_tag, value const& jv) { if( jv.is_null() ) return std::nullopt; error_code ec; BOOST_JSON_FAIL(ec, error::not_null); return ec; } #endif // std::variant #ifndef BOOST_NO_CXX17_HDR_VARIANT template result< std::variant > tag_invoke( try_value_to_tag< std::variant >, value const& jv) { error_code ec; BOOST_JSON_FAIL(ec, error::exhausted_variants); using Variant = std::variant; result res = {system::in_place_error, ec}; mp11::mp_for_each< mp11::mp_iota_c >([&](auto I) { if( res ) return; using T = std::variant_alternative_t; auto attempt = try_value_to(jv); if( attempt ) res.emplace(std::in_place_index_t(), std::move(*attempt)); }); if( res.has_error() ) { res = {system::in_place_error, ec}; } return res; } #endif // BOOST_NO_CXX17_HDR_VARIANT BOOST_JSON_NS_END #endif