mirror of
https://github.com/nlohmann/json.git
synced 2026-10-01 12:10:32 +00:00
Merge the duplicated UBJSON/BJData integer marker ladders
write_number_with_ubjson_prefix() (unsigned and signed overloads) and ubjson_prefix() (number_integer and number_unsigned cases) each picked the UBJSON/BJData integer marker (i, U, I, u, l, m, L, M, H) with their own independent if/else ladder, and the values beyond 64 bits were handled by a second, tag-dispatched pair of ladders. An optimized container announces the marker of its first element via ubjson_prefix() and then writes every element through write_number_with_ubjson_prefix(), so the two had to be kept in lockstep by hand across four call sites. Replace all of that with one ubjson_integer_prefix() built on value_in_range_of<T>, and one write_ubjson_integer_payload() that writes the value (or, for 'H', the decimal digits) for a given marker. write_number_with_ubjson_prefix() and ubjson_prefix() keep their signatures and now just call these two helpers. Behavior, the public API and the ABI are unchanged. Verified with a new regression test covering scalars and $-optimized arrays/objects at every int8/uint8/int16/uint16/int32/uint32/int64/uint64 boundary for to_ubjson/to_bjdata (both use_size/use_type settings), and by diffing to_ubjson/to_bjdata output before and after over the json_test_data corpus (bit-identical). Part of #5710 Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -1494,202 +1494,122 @@ class binary_writer
|
||||
write_number(n, use_bjdata);
|
||||
}
|
||||
|
||||
// UBJSON: write number (unsigned integer)
|
||||
// UBJSON: write number (integer)
|
||||
template<typename NumberType, typename std::enable_if<
|
||||
std::is_unsigned<NumberType>::value, int>::type = 0>
|
||||
std::is_integral<NumberType>::value, int>::type = 0>
|
||||
void write_number_with_ubjson_prefix(const NumberType n,
|
||||
const bool add_prefix,
|
||||
const bool use_bjdata)
|
||||
{
|
||||
if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('i')); // int8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('U')); // uint8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('I')); // int16
|
||||
}
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('u')); // uint16 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('l')); // int32
|
||||
}
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('m')); // uint32 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('M')); // uint64 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint64_t>(n), use_bjdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UBJSON: write number (signed integer)
|
||||
template < typename NumberType, typename std::enable_if <
|
||||
std::is_signed<NumberType>::value&&
|
||||
!std::is_floating_point<NumberType>::value, int >::type = 0 >
|
||||
void write_number_with_ubjson_prefix(const NumberType n,
|
||||
const bool add_prefix,
|
||||
const bool use_bjdata)
|
||||
{
|
||||
if ((std::numeric_limits<std::int8_t>::min)() <= n && n <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('i')); // int8
|
||||
}
|
||||
write_number(static_cast<std::int8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('U')); // uint8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int16_t>::min)() <= n && n <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('I')); // int16
|
||||
}
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('u')); // uint16 - bjdata only
|
||||
}
|
||||
write_number(static_cast<uint16_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('l')); // int32
|
||||
}
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('m')); // uint32 - bjdata only
|
||||
}
|
||||
write_number(static_cast<uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
// every value of an integer type of at most 64 bits fits into an
|
||||
// int64; only a wider type needs a range check
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
|
||||
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
|
||||
{
|
||||
const CharType prefix = ubjson_integer_prefix(n, use_bjdata);
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
oa.write_character(prefix);
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
write_ubjson_integer_payload(prefix, n, use_bjdata);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief determine the UBJSON/BJData type marker of an integer
|
||||
|
||||
This is the only place that picks the marker of an integer: both
|
||||
write_number_with_ubjson_prefix() and ubjson_prefix() use it. An optimized
|
||||
container announces the marker of its first value after `$` and then
|
||||
writes every value without a marker, so the two must never disagree.
|
||||
|
||||
@param[in] n the integer
|
||||
@param[in] use_bjdata whether the BJData-only markers `u`, `m`, and `M`
|
||||
may be used
|
||||
|
||||
@return the first marker of `i`, `U`, `I`, `u` (BJData), `l`, `m` (BJData),
|
||||
`L`, `M` (BJData, unsigned types only), and `H` (high-precision
|
||||
number) whose range contains @a n
|
||||
*/
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
|
||||
static CharType ubjson_integer_prefix(const NumberType n, const bool use_bjdata) noexcept
|
||||
{
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
if (value_in_range_of<std::int8_t>(n))
|
||||
{
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
|
||||
return;
|
||||
return 'i';
|
||||
}
|
||||
|
||||
if (add_prefix)
|
||||
if (value_in_range_of<std::uint8_t>(n))
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
return 'U';
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
if (value_in_range_of<std::int16_t>(n))
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && value_in_range_of<std::uint16_t>(n))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if (value_in_range_of<std::int32_t>(n))
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && value_in_range_of<std::uint32_t>(n))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if (value_in_range_of<std::int64_t>(n))
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H';
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the value of an integer for the marker chosen by
|
||||
ubjson_integer_prefix()
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
|
||||
void write_ubjson_integer_payload(const CharType prefix, const NumberType n, const bool use_bjdata)
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
|
||||
{
|
||||
// anything outside of the range of an int64 is treated as a
|
||||
// high-precision number
|
||||
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
|
||||
switch (prefix)
|
||||
{
|
||||
case 'i':
|
||||
write_number(static_cast<std::int8_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'U':
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'I':
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'u':
|
||||
write_number(static_cast<std::uint16_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'l':
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'm':
|
||||
write_number(static_cast<std::uint32_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'L':
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'M':
|
||||
write_number(static_cast<std::uint64_t>(n), use_bjdata);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// high-precision number: the decimal digits as a string
|
||||
JSON_ASSERT(prefix == 'H');
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1706,74 +1626,10 @@ class binary_writer
|
||||
return j.m_data.m_value.boolean ? 'T' : 'F';
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
return 'i';
|
||||
}
|
||||
if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
return 'U';
|
||||
}
|
||||
if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
// every value of an integer type of at most 64 bits fits into
|
||||
// an int64; only a wider type needs a range check
|
||||
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
|
||||
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
return ubjson_integer_prefix(j.m_data.m_value.number_integer, use_bjdata);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
|
||||
{
|
||||
return 'i';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))
|
||||
{
|
||||
return 'U';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
|
||||
{
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H';
|
||||
}
|
||||
return ubjson_integer_prefix(j.m_data.m_value.number_unsigned, use_bjdata);
|
||||
|
||||
case value_t::number_float:
|
||||
return get_ubjson_float_prefix(j.m_data.m_value.number_float);
|
||||
|
||||
@@ -21824,202 +21824,122 @@ class binary_writer
|
||||
write_number(n, use_bjdata);
|
||||
}
|
||||
|
||||
// UBJSON: write number (unsigned integer)
|
||||
// UBJSON: write number (integer)
|
||||
template<typename NumberType, typename std::enable_if<
|
||||
std::is_unsigned<NumberType>::value, int>::type = 0>
|
||||
std::is_integral<NumberType>::value, int>::type = 0>
|
||||
void write_number_with_ubjson_prefix(const NumberType n,
|
||||
const bool add_prefix,
|
||||
const bool use_bjdata)
|
||||
{
|
||||
if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('i')); // int8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('U')); // uint8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('I')); // int16
|
||||
}
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('u')); // uint16 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('l')); // int32
|
||||
}
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('m')); // uint32 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata)
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('M')); // uint64 - bjdata only
|
||||
}
|
||||
write_number(static_cast<std::uint64_t>(n), use_bjdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UBJSON: write number (signed integer)
|
||||
template < typename NumberType, typename std::enable_if <
|
||||
std::is_signed<NumberType>::value&&
|
||||
!std::is_floating_point<NumberType>::value, int >::type = 0 >
|
||||
void write_number_with_ubjson_prefix(const NumberType n,
|
||||
const bool add_prefix,
|
||||
const bool use_bjdata)
|
||||
{
|
||||
if ((std::numeric_limits<std::int8_t>::min)() <= n && n <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('i')); // int8
|
||||
}
|
||||
write_number(static_cast<std::int8_t>(n), use_bjdata);
|
||||
}
|
||||
else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)()))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('U')); // uint8
|
||||
}
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int16_t>::min)() <= n && n <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('I')); // int16
|
||||
}
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('u')); // uint16 - bjdata only
|
||||
}
|
||||
write_number(static_cast<uint16_t>(n), use_bjdata);
|
||||
}
|
||||
else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('l')); // int32
|
||||
}
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
}
|
||||
else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))
|
||||
{
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('m')); // uint32 - bjdata only
|
||||
}
|
||||
write_number(static_cast<uint32_t>(n), use_bjdata);
|
||||
}
|
||||
else
|
||||
{
|
||||
// every value of an integer type of at most 64 bits fits into an
|
||||
// int64; only a wider type needs a range check
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
|
||||
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
|
||||
{
|
||||
const CharType prefix = ubjson_integer_prefix(n, use_bjdata);
|
||||
if (add_prefix)
|
||||
{
|
||||
oa.write_character(to_char_type('L')); // int64
|
||||
oa.write_character(prefix);
|
||||
}
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
write_ubjson_integer_payload(prefix, n, use_bjdata);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief determine the UBJSON/BJData type marker of an integer
|
||||
|
||||
This is the only place that picks the marker of an integer: both
|
||||
write_number_with_ubjson_prefix() and ubjson_prefix() use it. An optimized
|
||||
container announces the marker of its first value after `$` and then
|
||||
writes every value without a marker, so the two must never disagree.
|
||||
|
||||
@param[in] n the integer
|
||||
@param[in] use_bjdata whether the BJData-only markers `u`, `m`, and `M`
|
||||
may be used
|
||||
|
||||
@return the first marker of `i`, `U`, `I`, `u` (BJData), `l`, `m` (BJData),
|
||||
`L`, `M` (BJData, unsigned types only), and `H` (high-precision
|
||||
number) whose range contains @a n
|
||||
*/
|
||||
template<typename NumberType>
|
||||
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
|
||||
static CharType ubjson_integer_prefix(const NumberType n, const bool use_bjdata) noexcept
|
||||
{
|
||||
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||
if (value_in_range_of<std::int8_t>(n))
|
||||
{
|
||||
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
|
||||
return;
|
||||
return 'i';
|
||||
}
|
||||
|
||||
if (add_prefix)
|
||||
if (value_in_range_of<std::uint8_t>(n))
|
||||
{
|
||||
oa.write_character(to_char_type('H')); // high-precision number
|
||||
return 'U';
|
||||
}
|
||||
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
if (value_in_range_of<std::int16_t>(n))
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && value_in_range_of<std::uint16_t>(n))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if (value_in_range_of<std::int32_t>(n))
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && value_in_range_of<std::uint32_t>(n))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if (value_in_range_of<std::int64_t>(n))
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H';
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the value of an integer for the marker chosen by
|
||||
ubjson_integer_prefix()
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
|
||||
void write_ubjson_integer_payload(const CharType prefix, const NumberType n, const bool use_bjdata)
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
|
||||
{
|
||||
// anything outside of the range of an int64 is treated as a
|
||||
// high-precision number
|
||||
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
|
||||
switch (prefix)
|
||||
{
|
||||
case 'i':
|
||||
write_number(static_cast<std::int8_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'U':
|
||||
write_number(static_cast<std::uint8_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'I':
|
||||
write_number(static_cast<std::int16_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'u':
|
||||
write_number(static_cast<std::uint16_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'l':
|
||||
write_number(static_cast<std::int32_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'm':
|
||||
write_number(static_cast<std::uint32_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'L':
|
||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||
break;
|
||||
case 'M':
|
||||
write_number(static_cast<std::uint64_t>(n), use_bjdata);
|
||||
break;
|
||||
default:
|
||||
{
|
||||
// high-precision number: the decimal digits as a string
|
||||
JSON_ASSERT(prefix == 'H');
|
||||
const auto number = BasicJsonType(n).dump();
|
||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||
for (std::size_t i = 0; i < number.size(); ++i)
|
||||
{
|
||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -22036,74 +21956,10 @@ class binary_writer
|
||||
return j.m_data.m_value.boolean ? 'T' : 'F';
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
return 'i';
|
||||
}
|
||||
if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
|
||||
{
|
||||
return 'U';
|
||||
}
|
||||
if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
// every value of an integer type of at most 64 bits fits into
|
||||
// an int64; only a wider type needs a range check
|
||||
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
|
||||
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||
}
|
||||
return ubjson_integer_prefix(j.m_data.m_value.number_integer, use_bjdata);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))
|
||||
{
|
||||
return 'i';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))
|
||||
{
|
||||
return 'U';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))
|
||||
{
|
||||
return 'I';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))
|
||||
{
|
||||
return 'u';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||
{
|
||||
return 'l';
|
||||
}
|
||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))
|
||||
{
|
||||
return 'm';
|
||||
}
|
||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata)
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
// anything else is treated as a high-precision number
|
||||
return 'H';
|
||||
}
|
||||
return ubjson_integer_prefix(j.m_data.m_value.number_unsigned, use_bjdata);
|
||||
|
||||
case value_t::number_float:
|
||||
return get_ubjson_float_prefix(j.m_data.m_value.number_float);
|
||||
|
||||
@@ -3033,3 +3033,224 @@ TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
||||
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||
CHECK(json::from_ubjson(expected) == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the bytes that follow the marker of an integer: the value in the width of
|
||||
// the marker (big endian for UBJSON, little endian for BJData), or, for a
|
||||
// high-precision number, the length and the decimal digits
|
||||
std::vector<std::uint8_t> integer_payload(const char marker, const json& value, const bool little_endian)
|
||||
{
|
||||
std::size_t width = 0;
|
||||
switch (marker)
|
||||
{
|
||||
case 'i':
|
||||
case 'U':
|
||||
width = 1;
|
||||
break;
|
||||
case 'I':
|
||||
case 'u':
|
||||
width = 2;
|
||||
break;
|
||||
case 'l':
|
||||
case 'm':
|
||||
width = 4;
|
||||
break;
|
||||
case 'L':
|
||||
case 'M':
|
||||
width = 8;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const std::string digits = value.dump();
|
||||
std::vector<std::uint8_t> result = {'i', static_cast<std::uint8_t>(digits.size())};
|
||||
for (const char c : digits)
|
||||
{
|
||||
result.push_back(static_cast<std::uint8_t>(c));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint64_t bits = value.is_number_unsigned()
|
||||
? value.get<std::uint64_t>()
|
||||
: static_cast<std::uint64_t>(value.get<std::int64_t>());
|
||||
std::vector<std::uint8_t> result(width);
|
||||
for (std::size_t i = 0; i < width; ++i)
|
||||
{
|
||||
result[little_endian ? i : width - 1 - i] = static_cast<std::uint8_t>(bits >> (8 * i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
json i64(const std::int64_t v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
json u64(const std::uint64_t v)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("UBJSON and BJData integer markers at every range edge")
|
||||
{
|
||||
// An optimized container announces the marker of its values after `$` and
|
||||
// then writes every value without a marker, so the marker the writer
|
||||
// announces and the width it writes must match for every value. This
|
||||
// checks both for the values around each edge of the integer types, as
|
||||
// scalars and as the values of optimized arrays and objects.
|
||||
struct integer_case
|
||||
{
|
||||
json value;
|
||||
char ubjson; // expected UBJSON marker
|
||||
char bjdata; // expected BJData marker
|
||||
};
|
||||
|
||||
const std::int64_t int64_min = (std::numeric_limits<std::int64_t>::min)();
|
||||
const std::int64_t int64_max = (std::numeric_limits<std::int64_t>::max)();
|
||||
const std::uint64_t uint64_max = (std::numeric_limits<std::uint64_t>::max)();
|
||||
|
||||
const std::vector<integer_case> cases =
|
||||
{
|
||||
// int8
|
||||
{i64(-129), 'I', 'I'},
|
||||
{i64(-128), 'i', 'i'},
|
||||
{i64(-127), 'i', 'i'},
|
||||
{i64(-1), 'i', 'i'},
|
||||
{i64(0), 'i', 'i'},
|
||||
{u64(0), 'i', 'i'},
|
||||
{i64(126), 'i', 'i'},
|
||||
{i64(127), 'i', 'i'},
|
||||
{u64(127), 'i', 'i'},
|
||||
{i64(128), 'U', 'U'},
|
||||
{u64(128), 'U', 'U'},
|
||||
// uint8
|
||||
{i64(254), 'U', 'U'},
|
||||
{i64(255), 'U', 'U'},
|
||||
{u64(255), 'U', 'U'},
|
||||
{i64(256), 'I', 'I'},
|
||||
{u64(256), 'I', 'I'},
|
||||
// int16
|
||||
{i64(-32769), 'l', 'l'},
|
||||
{i64(-32768), 'I', 'I'},
|
||||
{i64(-32767), 'I', 'I'},
|
||||
{i64(32766), 'I', 'I'},
|
||||
{i64(32767), 'I', 'I'},
|
||||
{u64(32767), 'I', 'I'},
|
||||
{i64(32768), 'l', 'u'},
|
||||
{u64(32768), 'l', 'u'},
|
||||
// uint16 (BJData only)
|
||||
{i64(65534), 'l', 'u'},
|
||||
{i64(65535), 'l', 'u'},
|
||||
{u64(65535), 'l', 'u'},
|
||||
{i64(65536), 'l', 'l'},
|
||||
{u64(65536), 'l', 'l'},
|
||||
// int32
|
||||
{i64(-2147483649LL), 'L', 'L'},
|
||||
{i64(-2147483648LL), 'l', 'l'},
|
||||
{i64(-2147483647LL), 'l', 'l'},
|
||||
{i64(2147483646LL), 'l', 'l'},
|
||||
{i64(2147483647LL), 'l', 'l'},
|
||||
{u64(2147483647ULL), 'l', 'l'},
|
||||
{i64(2147483648LL), 'L', 'm'},
|
||||
{u64(2147483648ULL), 'L', 'm'},
|
||||
// uint32 (BJData only)
|
||||
{i64(4294967294LL), 'L', 'm'},
|
||||
{i64(4294967295LL), 'L', 'm'},
|
||||
{u64(4294967295ULL), 'L', 'm'},
|
||||
{i64(4294967296LL), 'L', 'L'},
|
||||
{u64(4294967296ULL), 'L', 'L'},
|
||||
// int64
|
||||
{i64(int64_min), 'L', 'L'},
|
||||
{i64(int64_min + 1), 'L', 'L'},
|
||||
{i64(int64_max - 1), 'L', 'L'},
|
||||
{i64(int64_max), 'L', 'L'},
|
||||
{u64(static_cast<std::uint64_t>(int64_max)), 'L', 'L'},
|
||||
// uint64 (BJData only; UBJSON writes a high-precision number)
|
||||
{u64(static_cast<std::uint64_t>(int64_max) + 1), 'H', 'M'},
|
||||
{u64(uint64_max - 1), 'H', 'M'},
|
||||
{u64(uint64_max), 'H', 'M'},
|
||||
};
|
||||
|
||||
for (const auto& c : cases)
|
||||
{
|
||||
for (const bool bjdata :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
const char marker = bjdata ? c.bjdata : c.ubjson;
|
||||
const std::vector<std::uint8_t> payload = integer_payload(marker, c.value, bjdata);
|
||||
const auto to_binary = [bjdata](const json & j, const bool use_size, const bool use_type)
|
||||
{
|
||||
return bjdata ? json::to_bjdata(j, use_size, use_type) : json::to_ubjson(j, use_size, use_type);
|
||||
};
|
||||
const auto from_binary = [bjdata](const std::vector<std::uint8_t>& v)
|
||||
{
|
||||
return bjdata ? json::from_bjdata(v) : json::from_ubjson(v);
|
||||
};
|
||||
INFO("value = " << c.value.dump() << (c.value.is_number_unsigned() ? " (unsigned)" : "") << ", format = " << (bjdata ? "BJData" : "UBJSON"));
|
||||
|
||||
// scalar
|
||||
std::vector<std::uint8_t> expected = {static_cast<std::uint8_t>(marker)};
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CHECK(to_binary(c.value, use_size, false) == expected);
|
||||
}
|
||||
CHECK(from_binary(expected) == c.value);
|
||||
|
||||
const json arr = {c.value, c.value, c.value};
|
||||
|
||||
// array without count or type: every value has its marker
|
||||
expected = {'['};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
expected.push_back(']');
|
||||
CHECK(to_binary(arr, false, false) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// array with count: every value has its marker
|
||||
expected = {'[', '#', 'i', 3};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.push_back(static_cast<std::uint8_t>(marker));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(arr, true, false) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// array with type and count: the marker once, then the payloads
|
||||
expected = {'[', '$', static_cast<std::uint8_t>(marker), '#', 'i', 3};
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(arr, true, true) == expected);
|
||||
CHECK(from_binary(expected) == arr);
|
||||
|
||||
// object with type and count: the marker once, then key and payload
|
||||
const json obj = {{"a", c.value}, {"b", c.value}};
|
||||
expected = {'{', '$', static_cast<std::uint8_t>(marker), '#', 'i', 2};
|
||||
for (const char key :
|
||||
{'a', 'b'
|
||||
})
|
||||
{
|
||||
expected.push_back('i');
|
||||
expected.push_back(1);
|
||||
expected.push_back(static_cast<std::uint8_t>(key));
|
||||
expected.insert(expected.end(), payload.begin(), payload.end());
|
||||
}
|
||||
CHECK(to_binary(obj, true, true) == expected);
|
||||
CHECK(from_binary(expected) == obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user