Compare commits

..
Author SHA1 Message Date
Niels Lohmann 9c349b1d7c Write integers of 17 to 20 digits eight digits at a time in dump() (#5964)
#5634 made dump() write integers below 10^16 eight digits at a time; larger
ones still took the two-digits-per-division loop and count_digits(). Random
64-bit integers almost all have 17 to 20 digits (99.9 % of signed_ints.json
and unsigned_ints.json), so Dump/signed_ints and Dump/unsigned_ints gained
only 1.4x over 3.12.0, against 2.3x for small integers.

A 64-bit magnitude is now written as its first one to four digits and two
groups of eight. Wider magnitudes (__int128 and class types, #5805) keep the
loop: number types of at most 64 bits are selected by type, so no comparison
is reported as always true; for others the value is compared. The sign
handling of #1447/#5815 (remove_sign() into number_magnitude_t) is unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:45 +01:00
Niels Lohmann 367963f6bb Test escaped object keys decoded through NLOHMANN_DEFINE_TYPE from a json_view (#5972)
A json_view or json_document decodes a macro-defined struct directly and looks
the members up by their decoded keys. Only plain keys were covered so far.

The new section decodes structs defined with NLOHMANN_DEFINE_TYPE_INTRUSIVE,
..._WITH_DEFAULT and ..._WITH_NAMES from documents whose keys contain escape
sequences, and compares the result with the conversion of the materialized
value. It also pins the difference for a duplicate key written once plain and
once escaped (the view takes the first member, the materialized value keeps
the last one), and that an escaped backslash is part of the decoded key.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:20 +01:00
Niels Lohmann 4fe670b5f4 json_view: decode chains of unicode escapes in a loop (#5969)
After the first \uXXXX escape of a string, slow_string() now decodes the
escapes that follow it directly, while the next six bytes are \u and four hex
digits that are not a surrogate, instead of going round its main loop for each
of them (that is how Python's json.dumps writes non-ASCII text by default).
A surrogate, a bad digit, or fewer than six bytes leave the position at the
backslash: the main loop handles them as before and reports the same error at
the same position.

twitterescaped.json (simdjson-data, 549 KB, 33,036 escapes), json_document::parse(),
A/B in one binary, 6 code layouts, time after / before:

- Apple M1 Max, Apple clang 21: 0.897
- x86-64 Haswell, GCC 16.2: 0.907
- x86-64 Haswell, Clang 23: 0.910

The other files of the benchmark set (twitter, citm_catalog, canada, gsoc-2018,
apache_builds, instruments, tree-pretty, github_events, random, update-center,
semanticscholar-corpus, twitter_timeline) stay within the noise of each machine.

New test: chains of every length from 1 to 40 of escapes that give one, two,
and three bytes of UTF-8, followed by the end of the string, text, escapes,
surrogates, bad digits, and cut in every place, in keys and in values, against
json::parse().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-31 13:54:02 +01:00
12 changed files with 500 additions and 1448 deletions

No files matched your search

+27 -453
View File
@@ -22,7 +22,6 @@
#include <bit> //byteswap
#endif
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
#include <nlohmann/detail/input/json_sax.hpp>
@@ -742,13 +741,11 @@ class binary_reader
bool get_cbor_negative_integer()
{
NumberType number{};
return get_number(number) && emit_cbor_negative_integer(number);
}
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
{
return false;
}
/// pass the CBOR negative integer -1 - @a number to the SAX parser
template<typename NumberType>
bool emit_cbor_negative_integer(const NumberType number)
{
// the value is -1 - number, which fits into number_integer_t
// whenever number does; the outer cast undoes the integral promotion
// for number_integer_t types narrower than int
@@ -973,7 +970,7 @@ class binary_reader
case 0x95:
case 0x96:
case 0x97:
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
case 0x98: // array (one-byte uint8_t for n follows)
case 0x99: // array (two-byte uint16_t for n follow)
@@ -982,7 +979,7 @@ class binary_reader
{
std::uint64_t len{};
std::size_t size{};
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
}
case 0x9F: // array (indefinite length)
@@ -1136,10 +1133,16 @@ class binary_reader
return get_half_float(false);
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
return get_cbor_float<float>();
{
float number{};
return get_number(number) && emit_float(number);
}
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
return get_cbor_float<double>();
{
double number{};
return get_number(number) && emit_float(number);
}
default: // anything else (0xFF is handled inside the other types)
{
@@ -1148,16 +1151,6 @@ class binary_reader
}
}
/// read a single- or double-precision float (kept out of
/// parse_cbor_value, which is inlined into parse_cbor_internal)
template<typename NumberType>
JSON_HEDLEY_NEVER_INLINE
bool get_cbor_float()
{
NumberType number{};
return get_number(number) && emit_float(number);
}
/*!
@brief reports a nested indefinite-length CBOR string or byte array
@param[in] type_name name of the rejected string type
@@ -1683,157 +1676,6 @@ class binary_reader
return true;
}
/*!
@brief read a big-endian number of type @a NumberType at @a data
*/
template<typename NumberType>
NumberType load_big_endian(const unsigned char* data) const noexcept
{
NumberType result{};
std::memcpy(&result, data, sizeof(NumberType));
if (is_little_endian)
{
byte_swap(result);
}
return result;
}
/*!
@brief read the integers, floats, booleans, and nulls that follow in a
definite-length array, without the per-element bookkeeping of
@ref parse_cbor_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_cbor_value, which reads them the same way.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_cbor_scalar_run(std::size_t& remaining)
{
while (remaining != 0 && ia.bulk_remaining() >= 9)
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0x18:
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0x19:
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x1A:
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x1B:
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0x38:
size = 2;
accepted = emit_cbor_negative_integer(data[1]);
break;
case 0x39:
size = 3;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
break;
case 0x3A:
size = 5;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
break;
case 0x3B:
size = 9;
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xF4:
size = 1;
accepted = sax->boolean(false);
break;
case 0xF5:
size = 1;
accepted = sax->boolean(true);
break;
case 0xF6:
size = 1;
accepted = sax->null();
break;
case 0xFA:
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xFB:
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
default:
if (head <= 0x17)
{
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0x20 && head <= 0x37)
{
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
}
else
{
// any other value is read by parse_cbor_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the head byte
ia.bulk_skip(size);
chars_read += size;
current = head;
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
return true;
}
/*!
@brief open a definite-length array and read the scalars it begins with
(see @ref parse_cbor_scalar_run)
Only the start of an array is tried, which covers arrays of numbers
(e.g. coordinates) but costs arrays of other values only one test, not one
per element.
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_cbor_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || parse_cbor_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_cbor_value stays small enough to be inlined into
/// @ref parse_cbor_internal
JSON_HEDLEY_NEVER_INLINE
bool parse_cbor_array_start(std::true_type /*bulk*/)
{
return parse_cbor_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_cbor_value
bool parse_cbor_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a CBOR value and everything nested inside it
@@ -1863,7 +1705,6 @@ class binary_reader
// pop_back() below, which destroys the container_stack element
// it would otherwise alias
const container_frame top = container_stack.back();
const bool is_object = top.is_object;
bool at_end = false;
if (top.remaining != npos)
@@ -1875,7 +1716,7 @@ class binary_reader
{
// claim the element about to be read
--container_stack.back().remaining;
if (is_object)
if (top.is_object)
{
get();
}
@@ -1888,7 +1729,7 @@ class binary_reader
// Testing for it consumes a byte, which is the first byte
// of the next element when it is not one.
at_end = (get() == 0xFF);
fetch = is_object;
fetch = top.is_object;
}
if (at_end)
@@ -1905,7 +1746,7 @@ class binary_reader
continue;
}
if (is_object)
if (top.is_object)
{
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
{
@@ -2130,7 +1971,7 @@ class binary_reader
case 0x9D:
case 0x9E:
case 0x9F:
return enter_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
// fixstr
case 0xA0:
@@ -2261,13 +2102,13 @@ class binary_reader
case 0xDC: // array 16
{
std::uint16_t len{};
return get_number(len) && enter_msgpack_array(static_cast<std::size_t>(len));
return get_number(len) && enter_array(static_cast<std::size_t>(len));
}
case 0xDD: // array 32
{
std::uint32_t len{};
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len));
return get_number(len) && enter_array(conditional_static_cast<std::size_t>(len));
}
case 0xDE: // map 16
@@ -2609,138 +2450,6 @@ class binary_reader
}
}
/*!
@brief read the integers, floats, booleans, and nils that follow in a
MessagePack array, without the per-element bookkeeping of
@ref parse_msgpack_internal
Stops at the end of the array, at any other kind of value, and when fewer
than 9 bytes (the longest of these values) are left in the input, leaving
those to @ref parse_msgpack_value, which reads them the same way.
@param[in,out] remaining the number of elements of the array not yet read
@return whether the SAX parser accepted the values
*/
bool parse_msgpack_scalar_run(std::size_t& remaining)
{
while (remaining != 0 && ia.bulk_remaining() >= 9)
{
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
const unsigned char head = data[0];
std::size_t size = 0;
bool accepted = true;
switch (head)
{
case 0xC0: // nil
size = 1;
accepted = sax->null();
break;
case 0xC2: // false
size = 1;
accepted = sax->boolean(false);
break;
case 0xC3: // true
size = 1;
accepted = sax->boolean(true);
break;
case 0xCA: // float 32
size = 5;
accepted = emit_float(load_big_endian<float>(data + 1));
break;
case 0xCB: // float 64
size = 9;
accepted = emit_float(load_big_endian<double>(data + 1));
break;
case 0xCC: // uint 8
size = 2;
accepted = emit_unsigned(data[1]);
break;
case 0xCD: // uint 16
size = 3;
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
break;
case 0xCE: // uint 32
size = 5;
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
break;
case 0xCF: // uint 64
size = 9;
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
break;
case 0xD0: // int 8
size = 2;
accepted = emit_signed(static_cast<std::int8_t>(data[1]));
break;
case 0xD1: // int 16
size = 3;
accepted = emit_signed(load_big_endian<std::int16_t>(data + 1));
break;
case 0xD2: // int 32
size = 5;
accepted = emit_signed(load_big_endian<std::int32_t>(data + 1));
break;
case 0xD3: // int 64
size = 9;
accepted = emit_signed(load_big_endian<std::int64_t>(data + 1));
break;
default:
if (head <= 0x7F)
{
// positive fixint
size = 1;
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
}
else if (head >= 0xE0)
{
// negative fixint
size = 1;
accepted = sax->number_integer(static_cast<std::int8_t>(head));
}
else
{
// any other value is read by parse_msgpack_value
return true;
}
break;
}
// as if read by get() and get_number(): current is the last byte
ia.bulk_skip(size);
chars_read += size;
current = data[size - 1];
--remaining;
if (JSON_HEDLEY_UNLIKELY(!accepted))
{
return false;
}
}
return true;
}
/*!
@brief open a MessagePack array and read the scalars it begins with (see
@ref parse_msgpack_scalar_run)
@param[in] len the number of elements
@return whether the SAX parser accepted the array and the values read
*/
bool enter_msgpack_array(const std::size_t len)
{
return enter_array(len) && (len == 0 || parse_msgpack_array_start(std::integral_constant<bool, bulk_scan> {}));
}
/// read the scalars the array just opened begins with; out of line, so
/// that @ref parse_msgpack_value stays small enough to be inlined
JSON_HEDLEY_NEVER_INLINE
bool parse_msgpack_array_start(std::true_type /*bulk*/)
{
return parse_msgpack_scalar_run(container_stack.back().remaining);
}
/// input that is not contiguous: every value is read by parse_msgpack_value
bool parse_msgpack_array_start(std::false_type /*bulk*/) const noexcept
{
return true;
}
/*!
@brief read a MessagePack value and everything nested inside it
@@ -3546,107 +3255,6 @@ class binary_reader
return unexpected_byte("invalid byte", "value");
}
/*!
@brief read the elements of an optimized array of numbers (type marker
@a type) that are in the input, in one loop, without the
per-element bookkeeping of @ref parse_ubjson_internal
Values are emitted as @ref get_ubjson_value emits them. Types other than
the fixed-size integers and floats are left to @ref get_ubjson_value, as
are the elements the input ends before.
@param[in,out] remaining the number of elements not yet read
@param[in] type the type marker of the elements
@return whether the SAX parser accepted the values
*/
JSON_HEDLEY_NEVER_INLINE
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
{
const bool bjdata = input_format == input_format_t::bjdata;
switch (type)
{
case 'U':
return parse_ubjson_typed_run<std::uint8_t>(remaining);
case 'i':
return parse_ubjson_typed_run<std::int8_t>(remaining);
case 'I':
return parse_ubjson_typed_run<std::int16_t>(remaining);
case 'l':
return parse_ubjson_typed_run<std::int32_t>(remaining);
case 'L':
return parse_ubjson_typed_run<std::int64_t>(remaining);
case 'u':
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
case 'm':
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
case 'M':
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
case 'd':
return parse_ubjson_typed_run<float>(remaining);
case 'D':
return parse_ubjson_typed_run<double>(remaining);
default:
return true;
}
}
/// input that is not contiguous: every element is read by get_ubjson_value
bool parse_ubjson_typed_run(std::size_t& /*remaining*/, const char_int_type /*type*/, std::false_type /*bulk*/) const noexcept
{
return true;
}
template<typename NumberType>
bool parse_ubjson_typed_run(std::size_t& remaining)
{
constexpr std::size_t size = sizeof(NumberType);
const std::size_t count = (std::min)(remaining, ia.bulk_remaining() / size);
if (count == 0)
{
return true;
}
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
// UBJSON stores numbers big-endian, BJData little-endian
const bool swap = is_little_endian != (input_format == input_format_t::bjdata);
std::size_t read = 0;
bool accepted = true;
while (read < count && accepted)
{
NumberType number{};
std::memcpy(&number, data + (read * size), size);
if (swap)
{
byte_swap(number);
}
++read;
accepted = emit_ubjson_number(number);
}
// as if read by get_number(): current is the last byte read
ia.bulk_skip(read * size);
chars_read += read * size;
current = data[(read * size) - 1];
remaining -= read;
return accepted;
}
template<typename NumberType, enable_if_t<std::is_floating_point<NumberType>::value, int> = 0>
bool emit_ubjson_number(const NumberType number)
{
return emit_float(number);
}
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& std::is_signed<NumberType>::value, int > = 0 >
bool emit_ubjson_number(const NumberType number)
{
return emit_signed(number);
}
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& !std::is_signed<NumberType>::value, int > = 0 >
bool emit_ubjson_number(const NumberType number)
{
return emit_unsigned(number);
}
/*!
@return whether array creation completed
*/
@@ -3682,15 +3290,7 @@ class binary_reader
return false;
}
// the numbers that are in the input, in one loop; the rest (if
// the input ends early) one by one below
std::size_t remaining = size_and_type.first;
if (!nd_is_char && JSON_HEDLEY_UNLIKELY(!parse_ubjson_typed_run(remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {})))
{
return false;
}
for (std::size_t i = size_and_type.first - remaining; i < size_and_type.first; ++i)
for (std::size_t i = 0; i < size_and_type.first; ++i)
{
if (nd_is_char)
{
@@ -3750,8 +3350,7 @@ class binary_reader
container_stack.back().remaining = 0;
}
return size_and_type.second == 0
|| parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {});
return true;
}
return enter_array(detail::unknown_size());
@@ -4092,13 +3691,13 @@ class binary_reader
return sax->null();
case 0xFB:
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
return sax->number_float(static_cast<number_float_t>(-1.0), "");
case 0xFC:
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
return sax->number_float(static_cast<number_float_t>(0.0), "");
case 0xFD:
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
return sax->number_float(static_cast<number_float_t>(1.0), "");
case 0xFF: // empty string
{
@@ -4467,27 +4066,6 @@ class binary_reader
return current;
}
/// reverse the byte order of a double with a single byteswap(): GCC does
/// not merge the byte-wise swap of the generic version into one
static void byte_swap(double& number) noexcept
{
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
std::uint64_t bits{};
std::memcpy(&bits, &number, sizeof(bits));
bits = byteswap(bits);
std::memcpy(&number, &bits, sizeof(bits));
}
/// reverse the byte order of a float with a single byteswap()
static void byte_swap(float& number) noexcept
{
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
std::uint32_t bits{};
std::memcpy(&bits, &number, sizeof(bits));
bits = byteswap(bits);
std::memcpy(&number, &bits, sizeof(bits));
}
template<class NumberType>
static void byte_swap(NumberType& number)
{
@@ -4623,7 +4201,7 @@ class binary_reader
{
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
}
return sax->number_float(result, no_source);
return sax->number_float(result, "");
}
/*!
@@ -4683,7 +4261,7 @@ class binary_reader
}();
return sax->number_float((half & 0x8000u) != 0
? static_cast<number_float_t>(-val)
: static_cast<number_float_t>(val), no_source);
: static_cast<number_float_t>(val), "");
}
/*!
@@ -5008,10 +4586,6 @@ class binary_reader
/// the SAX parser
json_sax_t* sax = nullptr;
/// the (empty) source text of every number_float event: binary formats
/// have none, and a string_t built for each float would cost more
const string_t no_source{};
/// the containers that have been opened and not closed yet; see @ref container_frame
std::vector<container_frame> container_stack{};
@@ -1977,6 +1977,23 @@ class serializer
return;
}
// 17 to 20 digits: the first one to four, then twice eight
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
{
const auto value = static_cast<std::uint64_t>(abs_value);
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
const std::uint64_t low16 = value - (top * 10000000000000000u);
const std::uint64_t middle = low16 / 100000000u;
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
char* const p = buffer_ptr + n_chars;
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
write_buffer_pos += n_chars + 24 - leading;
return;
}
n_chars += count_digits(abs_value);
// spare 1 byte for '\0'
@@ -2122,6 +2139,23 @@ class serializer
}
}
/// whether every number_magnitude_t fits 64 bits
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
/// 64 bits (selected by type, as the comparison would be reported as
/// always true), else compared
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
{
return true;
}
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
{
return x <= 18446744073709551615u;
}
/*
* Helper function for dump_integer
*
+15
View File
@@ -343,6 +343,21 @@ class builder
}
arena_ensure(w, w_end, 4);
w = put_utf8(w, cp);
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
// the same error at the same position as it would without this loop.
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
{
const int next = hex_codepoint(p + 2);
if (next < 0 || (next & 0xF800) == 0xD800)
{
break;
}
p += 6;
arena_ensure(w, w_end, 4);
w = put_utf8(w, next);
}
}
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{
+286 -679
View File
File diff suppressed because it is too large. Load diff
+15
View File
@@ -1585,6 +1585,21 @@ class builder
}
arena_ensure(w, w_end, 4);
w = put_utf8(w, cp);
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
// the same error at the same position as it would without this loop.
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
{
const int next = hex_codepoint(p + 2);
if (next < 0 || (next & 0xF800) == 0xD800)
{
break;
}
p += 6;
arena_ensure(w, w_end, 4);
w = put_utf8(w, next);
}
}
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{
-75
View File
@@ -26,14 +26,11 @@ using nlohmann::ordered_json;
#include <limits>
#include <iostream>
#include <fstream>
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
// trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE
@@ -4749,75 +4746,3 @@ TEST_CASE("BJData optimized container type marker is validated for count 0")
CHECK(json::from_bjdata(vb) == json::binary({}));
}
}
TEST_CASE("BJData optimized arrays from contiguous and non-contiguous input")
{
// contiguous input reads the numbers of an array with a type marker in
// one loop, other input number by number; both must give the same events,
// value, or error for the complete document and for every truncation of
// it, and stop at every event a SAX handler rejects
json document =
{
{"u8", {0, 200, 255}},
{"i8", {-5, 100, -128}},
{"i16", {-30000, 1000, 30000}},
{"i32", {-2000000000, 5, 70000}},
{"i64", {-9000000000000000000LL, 1, 5000000000LL}},
{"f64", {1.5, -0.1, 2.5}},
{"pairs", {{1.25, -3.75}, {1.25, -3.75}, json::array()}},
{"mixed", {1, "a", 2.5, nullptr}},
{"u16", {40000, 1, 65535}},
{"u32", {4000000000U, 1, 70000}},
{"u64", {18446744073709551615ULL, 1, 5000000000ULL}},
{"nd", {{"_ArrayType_", "double"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {1.5, -2.5, 3.5, -4.5}}}},
{"nd8", {{"_ArrayType_", "uint8"}, {"_ArraySize_", {3}}, {"_ArrayData_", {1, 2, 3}}}},
};
const std::vector<std::uint8_t> encoded = json::to_bjdata(document, true, true);
CHECK(json::from_bjdata(encoded) == document);
for (std::size_t length = 0; length <= encoded.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(encoded.begin(), encoded.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_bjdata(contiguous, true, false).dump() == json::from_bjdata(bytewise.begin(), bytewise.end(), true, false).dump());
#if !defined(JSON_NOEXCEPTION)
std::string contiguous_error;
std::string bytewise_error;
try
{
static_cast<void>(json::from_bjdata(contiguous));
}
catch (const json::exception& e)
{
contiguous_error = e.what();
}
try
{
static_cast<void>(json::from_bjdata(bytewise.begin(), bytewise.end()));
}
catch (const json::exception& e)
{
bytewise_error = e.what();
}
CHECK(contiguous_error == bytewise_error);
#endif
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::bjdata)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 80; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(encoded.begin(), encoded.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(encoded, &from_contiguous, json::input_format_t::bjdata)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata));
}
}
-74
View File
@@ -29,9 +29,7 @@ using nlohmann::json;
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("CBOR")
@@ -2448,78 +2446,6 @@ TEST_CASE("CBOR SAX parsing stops at every event")
CHECK(count_events(std::vector<std::uint8_t>({0xBF, 0x61, 'a', 0x9F, 0x01, 0xFF, 0xFF})) == 6);
}
TEST_CASE("CBOR arrays of scalars from contiguous and non-contiguous input")
{
// contiguous input reads the integers, floats, booleans, and nulls an
// array begins with in one loop, other input value by value; both must
// give the same events, value, or error for the complete document and for
// every truncation of it, and stop at every event a SAX handler rejects
const std::vector<std::uint8_t> cbor =
{
0xA3, // map(3)
0x61, 'a', // "a"
0x98, 0x16, // array(22)
0x00, 0x17, 0x18, 0xFF, 0x19, 0x01, 0x00, // 0, 23, 255, 256
0x1A, 0x00, 0x01, 0x00, 0x00, // 65536
0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
0x20, 0x37, 0x38, 0xFF, 0x39, 0x01, 0x00, // -1, -24, -256, -257
0x3A, 0x00, 0x01, 0x00, 0x00, // -65537
0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -18446744073709551616 (a float)
0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // -9223372036854775808
0xF4, 0xF5, 0xF6, // false, true, null
0xFA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float32)
0xFB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
0xF9, 0x3C, 0x00, // 1.0 (float16, ends the loop)
0x01, 0x61, 'a', // 1, "a"
0xFB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
0x61, 'b', // "b"
0x83, // array(3)
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x82, 0xFB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xFB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x80, // []
0x61, 'c', // "c"
0x84, 0xA0, 0x01, 0xC1, 0x02, 0x81, 0x03 // [{}, 1, tag(1) 2, [3]]
};
const auto tags = json::cbor_tag_handler_t::ignore;
const json j = json::from_cbor(cbor, true, true, tags);
CHECK(j["a"].size() == 22);
CHECK(j["a"][5] == 18446744073709551615u);
CHECK(j["a"][11].is_number_float());
CHECK(j["a"][11] == -18446744073709551616.0);
CHECK(j["a"][12] == (std::numeric_limits<std::int64_t>::min)());
CHECK(j["a"][17] == -0.1);
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
CHECK(j["c"] == json::parse("[{}, 1, 2, [3]]"));
for (std::size_t length = 0; length <= cbor.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(cbor.begin(), cbor.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_cbor(contiguous, true, false, tags).dump() == json::from_cbor(bytewise.begin(), bytewise.end(), true, false, tags).dump());
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 60; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(cbor.begin(), cbor.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(cbor, &from_contiguous, json::input_format_t::cbor, true, false, false, tags)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::cbor, true, false, false, tags));
}
}
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
{
// Reading an indefinite-length string or byte array used to call itself
+53
View File
@@ -575,6 +575,59 @@ TEST_CASE("json_view builder: escapes between runs of text")
}
}
TEST_CASE("json_view builder: chains of unicode escapes")
{
// Consecutive \uXXXX escapes are decoded in a loop of their own. Chains of
// every length from 1 to 40 of escapes that give one, two, and three
// bytes of UTF-8, in upper and lower case, followed by what ends a chain:
// the end of the string, text, another escape, UTF-8, and everything that
// is not a valid escape (a bad digit at every position of the last
// escape, a lone surrogate, a surrogate pair, a cut in every place).
const std::array<const char*, 8> chain = {{"\\u0041", "\\u00e4", "\\u00E4", "\\u20ac", "\\u3042", "\\uFFFF", "\\u0000", "\\u07ff"}};
const std::array<const char*, 12> ends = {{"", "abc", "\\n", "\\u00e9", "\\ud83d\\ude00", "\\ud83d", "\\ude00", "\\ud83d\\u0041", "\\u12G4", "\\u12", "\xc3\xa4", "\\"}};
std::mt19937 rng(2027); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t length = 1; length <= 40; ++length)
{
for (int i = 0; i < 20; ++i)
{
std::string content = std::string(rng() % 20, 'a');
for (std::size_t k = 0; k < length; ++k)
{
content += chain[rng() % chain.size()];
}
const std::string whole = content;
for (const char* end : ends)
{
check_string(whole + end);
check_string(whole + end + std::string(rng() % 19, 'z'));
}
// a bad digit at every position of every escape near the end of the chain
for (std::size_t back = 1; back <= 6 && back <= length; ++back)
{
for (std::size_t digit = 2; digit < 6; ++digit)
{
std::string broken = whole;
broken[broken.size() - (6 * back) + digit] = "gG\"\x01 z"[rng() % 6];
check_string(broken);
}
}
// the text cut in every place of the last three escapes
for (std::size_t cut = 0; cut <= 18 && cut <= whole.size(); ++cut)
{
check_string(whole.substr(0, whole.size() - cut));
}
}
}
// a chain longer than the first growth of the arena, in a key and in a value
std::string long_chain;
for (int i = 0; i < 3000; ++i)
{
long_chain += chain[static_cast<std::size_t>(i) % chain.size()];
}
check_string(long_chain);
check_string(long_chain + "\\ud83d\\ude00" + long_chain);
}
TEST_CASE("json_view node integer bits")
{
using nlohmann::detail::view::integer_bits;
+24
View File
@@ -443,6 +443,30 @@ TEST_CASE("json_view from_json")
CHECK(d.root().materialize().get<point>().x == 3);
}
SECTION("escaped keys: members are found by their decoded names")
{
// keys are decoded before they are compared, as by parse(); a lookup
// that compares the raw bytes of the source would miss these members
const json_document d = json_document::parse(R"([{"\u0078": 1, "y": 2}, {"V\u0061lue": 3, "Te\u0078t": "t"}, {"\u0062": "escaped"}, {"x": 1, "\u0078": 3, "y": 2}])");
const json j = d.root().materialize();
CHECK(d.root()[0].get<point>().x == 1);
CHECK(d.root()[0].get<point>().y == 2);
CHECK((d.root()[0].get<point>() == j[0].get<point>()));
CHECK(d.root()[1].get<with_names>().value == 3);
CHECK(d.root()[1].get<with_names>().text == "t");
CHECK(d.root()[2].get<with_defaults>().b == "escaped");
CHECK(d.root()[2].get<with_defaults>().a == 1);
// a duplicate key written once plain and once escaped: still the first member
CHECK(d.root()[3].get<point>().x == 1);
CHECK(j[3].get<point>().x == 3);
#if !defined(JSON_NOEXCEPTION) && !JSON_DIAGNOSTICS && !JSON_DIAGNOSTIC_POSITIONS
// an escaped backslash is part of the decoded key: "x\\" is not "x"
const json_document e = json_document::parse(R"({"x\\": 1, "y": 2})");
CHECK(exception_of_call([&] { static_cast<void>(e.root().get<point>()); }) == exception_of_call([&] { static_cast<void>(e.root().materialize().get<point>()); }));
CHECK(exception_of_call([&] { static_cast<void>(e.root().get<point>()); }) == "[json.exception.out_of_range.403] key 'x' not found");
#endif
}
SECTION("editable documents and ordered_json")
{
json_editable_document e = json_editable_document::parse(R"({"x": 1, "y": 2})");
-97
View File
@@ -27,15 +27,12 @@ using nlohmann::json;
#include <sstream>
#include <iomanip>
#include <limits>
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("MessagePack")
@@ -2215,97 +2212,3 @@ TEST_CASE("MessagePack large strings and binaries (chunked reader)")
}
}
}
TEST_CASE("MessagePack arrays of scalars from contiguous and non-contiguous input")
{
// contiguous input reads the integers, floats, booleans, and nils an
// array begins with in one loop, other input value by value; both must
// give the same events, value, or error for the complete document and for
// every truncation of it, and stop at every event a SAX handler rejects
const std::vector<std::uint8_t> msgpack =
{
0x83, // map(3)
0xA1, 'a', // "a"
0xDC, 0x00, 0x16, // array 16 (22)
0x00, 0x7F, 0xCC, 0xFF, 0xCD, 0x01, 0x00, // 0, 127, 255, 256
0xCE, 0x00, 0x01, 0x00, 0x00, // 65536
0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 18446744073709551615
0xFF, 0xE0, 0xD0, 0x80, 0xD1, 0xFF, 0x00, // -1, -32, -128, -256
0xD2, 0xFF, 0xFE, 0xFF, 0xFF, // -65537
0xD3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -9223372036854775808
0xC2, 0xC3, 0xC0, // false, true, nil
0xCA, 0x3F, 0xC0, 0x00, 0x00, // 1.5 (float 32)
0xCB, 0xBF, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, // -0.1
0xA1, 'x', // "x" (ends the loop)
0x01, // 1
0xCB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2.5
0xD9, 0x01, 'y', // "y" (str 8)
0xC0, // nil
0xA1, 'b', // "b"
0x93, // fixarray(3)
0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x92, 0xCB, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // [1.25,
0xCB, 0xC0, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // -3.75]
0x90, // []
0xA1, 'c', // "c"
0x94, 0x80, 0x01, 0xC4, 0x01, 0x05, 0x91, 0x03 // [{}, 1, bin 8, [3]]
};
const json j = json::from_msgpack(msgpack);
CHECK(j["a"].size() == 22);
CHECK(j["a"][5] == 18446744073709551615u);
CHECK(j["a"][7] == -32);
CHECK(j["a"][11] == (std::numeric_limits<std::int64_t>::min)());
CHECK(j["a"][15] == 1.5);
CHECK(j["a"][16] == -0.1);
CHECK(j["a"][17] == "x");
CHECK(j["b"] == json::parse("[[1.25, -3.75], [1.25, -3.75], []]"));
CHECK(j["c"][2].is_binary());
for (std::size_t length = 0; length <= msgpack.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(msgpack.begin(), msgpack.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_msgpack(contiguous, true, false).dump() == json::from_msgpack(bytewise.begin(), bytewise.end(), true, false).dump());
#if !defined(JSON_NOEXCEPTION)
std::string contiguous_error;
std::string bytewise_error;
try
{
static_cast<void>(json::from_msgpack(contiguous));
}
catch (const json::parse_error& e)
{
contiguous_error = e.what();
}
try
{
static_cast<void>(json::from_msgpack(bytewise.begin(), bytewise.end()));
}
catch (const json::parse_error& e)
{
bytewise_error = e.what();
}
CHECK(contiguous_error == bytewise_error);
#endif
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::msgpack)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 60; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(msgpack.begin(), msgpack.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(msgpack, &from_contiguous, json::input_format_t::msgpack)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::msgpack));
}
}
+46
View File
@@ -23,6 +23,7 @@ using nlohmann::json;
#include <cstdint>
#include <sstream>
#include <iomanip>
#include <limits>
#include <string>
#include <vector>
@@ -248,6 +249,51 @@ TEST_CASE_TEMPLATE("serialization for extreme integer values", T, int32_t, uint3
}
}
TEST_CASE("integers of every digit count are written like std::to_string")
{
// dump_integer() writes up to 16 digits in two groups of eight and 17 to
// 20 digits as one to four digits and two groups of eight
std::vector<std::uint64_t> values;
std::uint64_t power = 1;
for (int digits = 1; digits <= 20; ++digits)
{
values.push_back(power); // 1 followed by zeros
values.push_back(power + 1);
values.push_back((power * 2) - 1);
if (digits < 20)
{
values.push_back((power * 10) - 1); // all 9s
values.push_back((power * 10) - power); // 9 followed by zeros
power *= 10;
}
}
values.push_back((std::numeric_limits<std::uint64_t>::max)());
values.push_back(static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()));
values.push_back(static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1);
std::uint64_t state = 0x9E3779B97F4A7C15u;
for (int i = 0; i < 20000; ++i)
{
state = (state * 6364136223846793005u) + 1442695040888963407u;
values.push_back(state >> (state % 61)); // every bit length
}
for (const auto v : values)
{
CAPTURE(v)
CHECK(json(v).dump() == std::to_string(v));
if (v <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
const auto s = static_cast<std::int64_t>(v);
CHECK(json(s).dump() == std::to_string(s));
CHECK(json(-s).dump() == std::to_string(-s));
}
}
CHECK(json((std::numeric_limits<std::int64_t>::min)()).dump() == "-9223372036854775808");
// in a container, so the digits are followed by other output
CHECK(json::array({std::uint64_t{18446744073709551615u}, std::int64_t{-1234567890123456789}, 1}).dump() == "[18446744073709551615,-1234567890123456789,1]");
}
TEST_CASE("dump with binary values")
{
auto binary = json::binary({1, 2, 3, 4});
-70
View File
@@ -21,15 +21,12 @@ using nlohmann::json;
#include <iostream>
#include <fstream>
#include <list>
#include <set>
#include "make_test_data_available.hpp"
#include "round_trip_corpus.hpp"
#include "test_utils.hpp"
#include "sax_countdown.hpp"
#include "sax_event_loggers.hpp"
using utils::SaxCountdown;
using utils::SaxEventLogger;
TEST_CASE("UBJSON")
@@ -3403,70 +3400,3 @@ TEST_CASE("UBJSON optimized container type marker is validated for count 0")
}
}
}
TEST_CASE("UBJSON optimized arrays from contiguous and non-contiguous input")
{
// contiguous input reads the numbers of an array with a type marker in
// one loop, other input number by number; both must give the same events,
// value, or error for the complete document and for every truncation of
// it, and stop at every event a SAX handler rejects
json document =
{
{"u8", {0, 200, 255}},
{"i8", {-5, 100, -128}},
{"i16", {-30000, 1000, 30000}},
{"i32", {-2000000000, 5, 70000}},
{"i64", {-9000000000000000000LL, 1, 5000000000LL}},
{"f64", {1.5, -0.1, 2.5}},
{"pairs", {{1.25, -3.75}, {1.25, -3.75}, json::array()}},
{"mixed", {1, "a", 2.5, nullptr}},
};
const std::vector<std::uint8_t> encoded = json::to_ubjson(document, true, true);
CHECK(json::from_ubjson(encoded) == document);
for (std::size_t length = 0; length <= encoded.size(); ++length)
{
CAPTURE(length)
const std::vector<std::uint8_t> contiguous(encoded.begin(), encoded.begin() + static_cast<std::ptrdiff_t>(length));
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
CHECK(json::from_ubjson(contiguous, true, false).dump() == json::from_ubjson(bytewise.begin(), bytewise.end(), true, false).dump());
#if !defined(JSON_NOEXCEPTION)
std::string contiguous_error;
std::string bytewise_error;
try
{
static_cast<void>(json::from_ubjson(contiguous));
}
catch (const json::exception& e)
{
contiguous_error = e.what();
}
try
{
static_cast<void>(json::from_ubjson(bytewise.begin(), bytewise.end()));
}
catch (const json::exception& e)
{
bytewise_error = e.what();
}
CHECK(contiguous_error == bytewise_error);
#endif
SaxEventLogger from_contiguous;
SaxEventLogger from_bytewise;
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::ubjson)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::ubjson));
CHECK(from_contiguous.events == from_bytewise.events);
}
for (int events = 0; events <= 80; ++events)
{
CAPTURE(events)
const std::list<std::uint8_t> bytewise(encoded.begin(), encoded.end());
SaxCountdown from_contiguous(events);
SaxCountdown from_bytewise(events);
CHECK(json::sax_parse(encoded, &from_contiguous, json::input_format_t::ubjson)
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::ubjson));
}
}