Avoiding Physical Dependency
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] );
}
}