...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
JSON numbers are represented using std::int64_t
,
std::uint64_t
, and double
.
When a value
is constructed from an unsigned integer, its kind
will be kind::uint64
.
Likewise, a value
constructed from a signed integer will have kind::int64
,
or kind::double_
if constructed from a floating-point
type:
// construction from int value jv1 = 1; assert( jv1.is_int64() ); // construction from unsigned int value jv2 = 2u; assert( jv2.is_uint64() ); // construction from double value jv3 = 3.0; assert( jv3.is_double() );
When accessing a number contained within a value
, the function used must match
the value's kind
exactly; no conversions will be performed. For example if as_double
is called on a value
that contains a std::uint64_t
,
an exception is thrown. Similarly, the function if_double
will return nullptr
and calling
get_double
will result in
undefined behavior:
value jv = 1; assert( jv.is_int64() ); // jv.kind() != kind::uint64; throws std::uint64_t r1 = jv.as_uint64(); // jv.kind() != kind::uint64; the behavior is undefined std::uint64_t r2 = jv.get_uint64(); // if_double will always return nullptr, branch is not taken if(double* d = jv.if_double()) assert( false );
In cases where you know that a value
contains a number but don't
know its kind
,
value_to
can be used to convert the value
to an arithmetic type:
struct convert_int64 { value jv; operator int() const { return value_to< int >( this->jv ); } };
If the value
does not contain a number, or if the conversion is to an integer type T
and the number cannot be represented
exactly by T
, the conversion
will fail. Otherwise, the result is the number converted to T
as-if by static_cast
:
value jv1 = 404; assert( jv1.is_int64() ); // ok, identity conversion std::int64_t r1 = value_to< std::int64_t >( jv1 ); // loss of data, throws system_error char r2 = value_to< char >( jv1 ); // ok, no loss of data double r3 = value_to< double >( jv1 ); value jv2 = 1.23; assert( jv1.is_double() ); // ok, same as static_cast<float>( jv2.get_double() ) float r4 = value_to< float >( jv2 ); // not exact, throws system_error int r5 = value_to< int >( jv2 ); value jv3 = {1, 2, 3}; assert( ! jv3.is_number() ); // not a number, throws system_error int r6 = value_to< int >( jv3 );
Arithmetic conversions done by value_to
delegate to value::to_number
. In settings where exceptions
cannot be used, an overload of value::to_number
accepting error_code
can be used instead with
identical semantics to its throwing counterpart:
value jv = 10.5; error_code ec; // ok, conversion is exact float r1 = jv.to_number< float >( ec ); assert( ! ec ); // error, conversion is non-exact int r2 = jv.to_number< int >( ec ); assert( ec == error::not_exact );
When parsing a JSON document, the type used to represent a number is not
explicitly specified and must be determined from its value. In general, the
parser will choose the best type which can accurately store the number as
it appears in the document. Integers (i.e. numbers without decimals or exponents)
that cannot be represented by std::uint64_t
and std::int64_t
will be represented as double
to preserve their magnitude:
value jv = parse("[-42, 100, 10.25, -299999999999999999998, 2e32]"); array ja = jv.as_array(); // represented by std::int64_t assert( ja[0].is_int64() ); // represented by std::int64_t assert( ja[1].is_int64() ); // contains decimal point, represented as double assert( ja[2].is_double() ); // less than INT64_MIN, represented as double assert( ja[3].is_double() ); // contains exponent, represented as double assert( ja[4].is_double() );
More formally, if the number:
INT64_MIN
,
or
UINT64_MAX
,
then its type is double
. Otherwise,
if the number is positive and its value is greater than INT64_MAX
,
then its type is std::uint64_t
. All other numbers are parsed
as std::int64_t
.