![]() |
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:
to_value_traits for T containing the public static member
function void to_json_traits<T>::assign( value&, T
const&
),
value_cast_traits for T containing the public static member
function T value_cast_traits<T>::construct( value const& ),
void
T::to_json(
value&
) const,
T::T( value const& ),
The library provides built-in support for converting between a value and the type T when:
std::is_constructible<
value,
T,
storage_ptr >::value ==
true, or
T is a container meeting
a built-in list of supported generic requirements.
When more than one conversion is available for a given type, one will be chosen according to a defined set of priorities.
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 );
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 |
|---|---|
Users should not directly invoke any |