![]() |
Home | Libraries | People | FAQ | More |
Some users, particularly library authors, may wish to provide conversions
between their types and value, but at the same time would
prefer to avoid having their library depend on Boost.JSON. This is possible
to achieve with the help of a few forward declarations.
namespace boost { namespace json { class value; struct value_from_tag; template< class T > struct try_value_to_tag; template< class T1, class T2 > struct result_for; template< class T > void value_from( T&& t, value& jv ); template< class T > typename result_for< T, value >::type try_value_to( const value& jv ); } }
Note that value_from is declared using an
out-parameter, rather then returning its result. This overload is specifically
designed for this use-case.
After that the definitions of tag_invoke
overloads should be provided. These overloads have to be templates, since
value
is only forward-declared and hence is an incomplete type.
namespace user_ns { template< class JsonValue > void tag_invoke( const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr ) { const unsigned char* b = addr.begin(); jv = { b[0], b[1], b[2], b[3] }; } template< class JsonValue > typename boost::json::result_for< ip_address, JsonValue >::type tag_invoke( const boost::json::try_value_to_tag< ip_address >&, const JsonValue& jv ) { using namespace boost::json; if( !jv.is_array() ) return make_error_code( std::errc::invalid_argument ); auto const& arr = jv.get_array(); if( arr.size() != 4 ) return make_error_code( std::errc::invalid_argument ); auto oct1 = try_value_to< unsigned char >( arr[0] ); if( !oct1 ) return make_error_code( std::errc::invalid_argument ); auto oct2 = try_value_to< unsigned char >( arr[1] ); if( !oct2 ) return make_error_code( std::errc::invalid_argument ); auto oct3 = try_value_to< unsigned char >( arr[2] ); if( !oct3 ) return make_error_code( std::errc::invalid_argument ); auto oct4 = try_value_to< unsigned char >( arr[3] ); if( !oct4 ) return make_error_code( std::errc::invalid_argument ); return ip_address{ *oct1, *oct2, *oct3, *oct4 }; } }
As discussed previously, we prefer to define a non-throwing overload of
tag_invoke for try_value_to, rather then the throwing
overload for value_to, as the latter can fallback
to the former without performance degradation.
Forward declarations of contextual conversions are done very similarly:
namespace boost { namespace json { class value; struct value_from_tag; template< class T > struct try_value_to_tag; template< class T1, class T2 > struct result_for; template< class T, class Context > void value_from( T&& t, value& jv, const Context& ctx ); template< class T, class Context > typename result_for< T, value >::type try_value_to( const value& jv, const Context& ctx ); } }
namespace user_ns { struct as_string { }; template< class JsonValue > void tag_invoke( const boost::json::value_from_tag&, JsonValue& jv, const ip_address& addr, const as_string& ) { auto& js = jv.emplace_string(); js.resize( 4 * 3 + 3 + 1 ); // XXX.XXX.XXX.XXX\0 auto it = addr.begin(); auto n = std::sprintf( js.data(), "%hhu.%hhu.%hhu.%hhu", it[0], it[1], it[2], it[3] ); js.resize(n); } template< class JsonValue > typename boost::json::result_for< ip_address, JsonValue >::type tag_invoke( const boost::json::try_value_to_tag< ip_address >&, const JsonValue& jv, const as_string& ) { const auto* js = jv.if_string(); if( ! js ) return make_error_code( std::errc::invalid_argument ); unsigned char octets[4]; int result = std::sscanf( js->data(), "%hhu.%hhu.%hhu.%hhu", octets, octets + 1, octets + 2, octets + 3 ); if( result != 4 ) return make_error_code( std::errc::invalid_argument ); return ip_address( octets[0], octets[1], octets[2], octets[3] ); } }