Boost.JSON Logo

PrevUpHomeNext

Value Conversion

While the value container makes it easy to create ad-hoc structures, often it is necessary to convert between JSON and specific user-defined types. The library provides a set of customization points to enable conversions between objects of type T and a JSON value by:

The library provides built-in support for converting between a value and the type T when:

When more than one conversion is available for a given type, one will be chosen according to a defined set of priorities.

Member Functions

The example code that follows uses the user-defined type customer, declared thusly:

struct customer
{
    std::uint64_t id;
    std::string name;
    bool delinquent;

    customer() = default;

    explicit customer( value const& );

    void to_json( value& jv ) const;
};

Since the author has control of the type, it uses member functions to enable conversions with JSON. An implementation of to_json may look like this:

void customer::to_json( value& jv ) const
{
    // Assign a JSON value
    jv = {
        { "id", id },
        { "name", name },
        { "delinquent", delinquent }
    };
}

Turning a JSON value back into a customer is a bit more complicated, as it must handle the case where the value does not have the necessary keys, or if the values have the wrong kind. The implementation of from_json throws an exception when the JSON value does not precisely match the expected schema for customer:

customer::customer( value const& jv )

    // at() throws if `jv` is not an object, or if the key is not found.
    //
    // as_uint64() will throw if the value is not an unsigned 64-bit integer.

    : id( jv.at( "id" ).as_uint64() )

    // We already know that jv is an object from
    // the previous call to jv.as_object() suceeding,
    // now we use jv.get_object() which skips the
    // check.
    //
    // value_cast will throw if jv.kind() != kind::string

    , name( value_cast< std::string >( jv.get_object().at( "name" ) ) )
{
    // id and name are constructed from JSON in the member
    // initializer list above, but we can also use regular
    // assignments in the body of the function as shown below.
    //
    // as_bool() will throw if kv.kind() != kind::bool

    this->delinquent = jv.get_object().at( "delinquent" ).as_bool();
}

When conversion between T and value is defined in both directions, round trips are possible:

customer cust;
cust.id = 1;
cust.name = "John Doe";
cust.delinquent = false;

// Convert customer to value
value jv = to_value( cust );

// Store value in customer
customer cust2;
cust2 = value_cast< customer >( jv );
Trait Specialization

Sometimes it is desired to perform JSON conversion, but the type in question cannot be modified. This can happen for std types, or for types from other libraries. Or, because the user wants to make keep the convertibility to and from JSON private and thus forgo adding a public member function. For these cases, it is possible to specialize the library class templates to_value_traits and value_cast_traits for the type and provide the necessary conversion functions. Here we enable conversion in both directions for the type std::complex:

// Specializations of to_value_traits and value_cast_traits
// must be declared in the boost::json namespace.

namespace boost {
namespace json {

template<class T>
struct to_value_traits< std::complex< T > >
{
    static void assign( value& jv, std::complex< T > const& t );
};

template<class T>
struct value_cast_traits< std::complex< T > >
{
    static std::complex< T > construct( value const& jv );
};

} // namespace json
} // namespace boost

The implementation for to_json is straightforward:

template< class T >
void
to_value_traits< std::complex< T > >::
assign( boost::json::value& jv, std::complex< T > const& t )
{
    // Store a complex number as a 2-element array
    array& a = jv.emplace_array();

    // Real part first
    a.emplace_back( t.real() );

    // Imaginary part last
    a.emplace_back( t.imag() );
}

As before, the implementation for from_json must use exceptions to indicate that the value does not have the right schema:

template< class T >
std::complex< T >
value_cast_traits< std::complex< T > >::
construct( value const& jv )
{
    // as_array() throws if jv.kind() != kind::array.

    array const& a = jv.as_array();

    // We store the complex number as a two element
    // array with the real part first, and the imaginary
    // part last.
    //
    // value_cast() throws if the JSON value does
    // not contain an applicable kind for the type T.

    if(a.size() != 2)
        throw std::invalid_argument(
            "invalid json for std::complex");
    return {
        value_cast< T >( a[0] ),
        value_cast< T >( a[1] ) };
}

Conversion to and from std::complex is now possible:

std::complex< double > c = { 3.14159, 2.71828 };

// Convert std::complex< double > to value
value jv = to_value(c);

// Store value in std::complex< double >
std::complex< double > c2;

c2 = value_cast< std::complex< double > >( jv );

Since the specializations of to_value_traits and value_cast_traits are class templates themselves, conversion to complex numbers using different floating point representations are also possible:

// Use float instead of double.

std::complex< float > c = { -42.f, 1.41421f };

value jv = to_value(c);

std::complex< float > c2;

c2 = value_cast< std::complex< float > >( jv );
[Warning] Warning

Users should not directly invoke any to_json member functions or the library class templates to_value_traits and value_cast_traits, otherwise the behavior may be undefined. Instead, callers should always use the functions to_value and value_cast, even in their implementation of the customization points for desired types.


PrevUpHomeNext