Compare commits

..
Author SHA1 Message Date
Niels Lohmann f5cb977387 Rename the key index's match callback so Flawfinder doesn't read it as std::equal
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:42:44 +02:00
Niels Lohmann 4cde5e39b9 Collect the members of ordered_json objects on a stack until they are known
An ordered_json object keeps its members in a vector and appends every new
member to it, so reading an object of unknown size reallocated the vector at
1, 2, 4, 8, ... members, and ordered_map copies all keys on every
reallocation. The DOM SAX parser now collects the members of such objects on
one stack shared by all open objects, as it does the elements of arrays, and
moves them into a vector of exactly the right size when the closing brace
arrives. An object that gets past 32 members switches to appending to its
own vector (and, from 64, to the key index of the earlier commit). The
members are the same, in the same order, and a key that occurs twice keeps
the place of its first occurrence. std::map based objects, objects whose
size the input announces (binary formats), and builds with JSON_DIAGNOSTICS
or JSON_DIAGNOSTIC_POSITIONS are untouched.

What only ordered objects need (the collected members, the key indexes) is
a member of the parser only for them, and the code that stores a value tries
the array that takes its elements directly first. An array that outgrows
the stack now reserves 32 elements, the capacity a vector that grew by
itself has at that point; with the larger reserve before, parsing
floats.json (1 000 000 numbers) was 2 % slower on Apple M1 than before this
series.

ordered_json parse, Linux Ir against develop (GCC / Clang): citm -11.6 % /
-11.7 %, twitter -8.4 % / -8.4 %, jeopardy (12 000 objects of 7 keys)
-10.5 % / -8.0 %, canada -3.9 % / -5.2 %, 124-byte object -20.9 % / -16.5 %.
macOS time (M1, earlier base): citm -19 %, twitter -13 %, jeopardy -13 %,
canada -14 %, 124-byte object -26 %. json parse: steady-state twitter -0.1 %
(see the previous commits), citm -1.2 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:08 +02:00
Niels Lohmann 284ef38125 Do not convert floats that cannot overflow in accept()
accept() only has to find out whether a float token is finite: json_sax_acceptor
ignores the value, and the parser rejects only a float that overflows. A
float whose digits fit into 19 digits and whose decimal exponent is at most
max_exponent10 - 19 is below 10^max_exponent10 and cannot overflow, so the
lexer, told by accept() that the value is not needed, skips the conversion
for those. A token that may overflow, with more digits, or in another float
format is converted as before, so 1e309 and its neighbours are rejected as
they were (the new test compares accept() with parse() on tokens around the
limit).

json::accept(): canada.json Ir -8.6 % (GCC) / -10.9 % (Clang), 124-byte
object -3.2 %; cycles on the M1 (release benchmarks, LTO): canada -7.8 %,
floats.json -9.0 %. The check tests the accept() flag first, so the parser
that needs the value pays one load and branch: SaxParse canada +0.35 % Ir
(GCC and Clang), DOM canada +0.4 %.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:05 +02:00
Niels Lohmann e38bb5d6f8 Test parsing containers of the sizes where the DOM parser changes storage
The DOM parser collects the elements of an array on a stack (up to 16) and
the members of an ordered_json object (up to 32) before it moves them into
the container, and uses a hash table for keys from 64 members on. Parse
arrays and objects of the sizes around these limits, nested into each other,
with repeated keys, and as random trees that are built next to their text,
and compare json and ordered_json against the value built through the API.

The tests pass on the code from before these changes as well. The random
trees are what finds the mistakes that are easy to make here: while the
collecting change was being written, a collected array that outgrew the
stack at the moment its parent had just reached the limit left its elements
in the parent, and only these tests (142 failed assertions) showed it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:03 +02:00
Niels Lohmann ffa9babc8d Trim the work the DOM parser does per container and per member
Opening and closing an array or object cost the DOM parser more than before
the exact-size array change, which showed on documents of many small
objects (jeopardy.json: +1.4 % Ir with GCC). This takes it back:

- the stack of open containers keeps its first eight entries in the parser
  object instead of in a vector, so it needs no capacity check beyond the
  push itself and no allocation for typical documents;
- a container is created in the place it ends up in, instead of as a
  temporary that is then moved there;
- a new object member, which is null, is constructed over the null instead
  of being swapped with it (only with the default base class of basic_json);
- the key index that big ordered_json objects use is only a part of the
  stack entries of objects that can have one;
- an array that outgrows the stack reserves twice what it has so far, as a
  vector that grew by itself would have.

DOM parse Ir of the series up to here against develop, Linux GCC / Clang:
canada -6.6 % / -7.3 %, citm +1.2 % / +0.2 %, jeopardy +0.4 % / -0.1 %,
124-byte object -6.5 % / -6.2 %. (twitter measures +6.8 % / +6.6 % at this
point in steady state, almost all of it in glibc's malloc/consolidate for the
new allocation sizes; with the last commit of the series it is -0.1 % / 0.0 %.)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:28:00 +02:00
Niels Lohmann 5b08041308 Look up the keys of big ordered_json objects in a hash table
ordered_map keeps its elements in a vector and searches it linearly for
every key it adds, so parsing an object with n keys into ordered_json took
O(n^2) key comparisons: 33 billion instructions for one object with 30 000
keys, where json needs 245 million. From 64 keys on, the DOM parser now
keeps a hash table of the keys of the open object (hashes and positions
only; the elements stay in the vector) and finds duplicate keys there. The
result is the same, including the position of a key that occurs twice.
Smaller objects, std::map based objects and key types without std::hash
are untouched.

ordered_json parse, macOS instructions: object with 1 000 keys 64.8 M ->
14.6 M, 10 000 keys 5 104 M -> 38.7 M, 30 000 keys 33 358 M -> 89 M.
Linux Ir of an object with 2 000 keys: 43.0 M -> 1.9 M (GCC), 37.9 M ->
1.8 M (Clang).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:58 +02:00
Niels Lohmann eef9aab5ba Remember where the DOM parser stores the next value
The DOM SAX parser looked up the innermost open container for every value:
the end of its frame stack, then the object or array pointer in the frame.
Compilers have to reload all of that after each value is stored, since a
store of a value may alias the frame. The parser now keeps the innermost
container's kind and, for an array that stores directly, the array itself
in members, updated when a container opens or closes.

Flat arrays of numbers are the case that mattered: parsing floats.json
(1 000 000 doubles) into a DOM took 2 % more cycles than before the
previous commit on macOS and now takes 0.4 % fewer than the code before it
(signed_ints.json -0.8 %). Linux Ir is unchanged (canada -6.1 % GCC /
-6.9 % Clang, citm -0.3 % / -0.8 %, twitter +0.2 % / 0.0 %).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:55 +02:00
Niels Lohmann c27f770c07 Reuse one value for the key events of the callback parser
parse() with a callback built a new string value for every key it passed to
the callback (a heap allocation and release per key), and copied the key a
second time into the object. The callback parser now keeps one string value
for key events, which it assigns, and moves the key into the object.

Callback parse, Linux Ir (GCC / Clang): twitter -13.3 % / -11.7 %, citm
-9.5 % / -7.6 %. (Earlier macOS measurement: twitter -19.5 % instructions.)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:53 +02:00
Niels Lohmann 2e454713d0 Build arrays of the DOM parser in one exact-size allocation
Reading an array of unknown length appended its elements to a std::vector
one at a time, so a small array reallocated (and moved its elements) at
sizes 1, 2, 4, ... The DOM SAX parser now collects the elements of arrays
whose length is unknown on one stack shared by all open arrays and moves
them into an array of exactly the right size when the closing bracket
arrives. An array that grows past 16 elements switches to appending to its
own vector, so large arrays pay no second copy. Binary formats, which
announce the length, and builds with JSON_DIAGNOSTICS or
JSON_DIAGNOSTIC_POSITIONS (whose values need their final place from the
start) keep storing into the array directly.

The stacks of open containers are reserved on first use instead of growing
from one element.

The binary readers get a DOM parser of their own, json_sax_dom_binary_parser:
it is the plain parser that stores every value directly in its container
(the code the readers used before). Their containers announce their size, so
the bookkeeping of the collecting parser (frame entries, the destination of a
finished container) was pure overhead for them: with the collecting parser
FromBinaryShape nested was 3-12 % and containers up to 5 % more instructions
(GCC) than before. With the plain parser Linux Ir of FromBinaryShape and
FromBinaryBuffer is within 0.8 % of develop (Clang: identical).

canada.json has 56 000 two-element arrays that took three allocations each
and now take two. DOM parse Ir on Linux against develop (GCC / Clang): canada
-7.0 % / -7.7 %, citm -0.8 % / -1.6 %, 124-byte object -5.4 % / -5.5 %;
accept and SAX are untouched.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:51 +02:00
Niels Lohmann 56ea5dd6bf Reserve the parser's nesting stack once
The stack of open arrays and objects was a std::vector<std::uint8_t> that
allocated at the first push_back and then reallocated at 1, 2, 4, 8 bytes,
so a document nested four levels deep made four allocations before it
parsed a value. A document that starts with a container now reserves 32
levels in one allocation; deeper documents grow as before. The loop that
reads the tokens is the code it was, so large documents are not affected
(a stack object with its own storage, and a reserve at every container,
both cost the token loop instructions or cycles).

Fixed cost of parsing a 124-byte object (macOS instructions): accept
-7.6 %, DOM -2.5 %; no change for a single number.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 09:27:48 +02:00
24 changed files with 2599 additions and 1350 deletions

No files matched your search

+1 -2
View File
@@ -80,8 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average. constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array index (unlike `BasicJsonType`'s array, which is random-access).
or object, since the elements are then adjacent in the index.
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.). that level (as 1.) or the index into the array (as 2.).
+1 -2
View File
@@ -28,8 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
index. Constant for an array none of whose elements is an array or object, since the elements are then adjacent index.
in the index.
## Notes ## Notes
@@ -86,8 +86,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average. constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the 2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array index (unlike `BasicJsonType`'s array, which is random-access).
or object, since the elements are then adjacent in the index.
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.). that level (as 1.) or the index into the array (as 2.).
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
/*! /*!
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values @brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
*/ */
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>> template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
class binary_reader class binary_reader
{ {
using number_integer_t = typename BasicJsonType::number_integer_t; using number_integer_t = typename BasicJsonType::number_integer_t;
File diff suppressed because it is too large. Load diff
+10
View File
@@ -14,6 +14,7 @@
#include <cstdio> // snprintf #include <cstdio> // snprintf
#include <cstring> // memcmp #include <cstring> // memcmp
#include <initializer_list> // initializer_list #include <initializer_list> // initializer_list
#include <limits> // numeric_limits
#include <string> // char_traits, string #include <string> // char_traits, string
#include <utility> // move #include <utility> // move
#include <vector> // vector #include <vector> // vector
@@ -1816,6 +1817,15 @@ scan_number_done:
/// significand (see scan_number_bulk_contiguous()) /// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{ {
// accept() only needs to know that the value is finite (see the parser):
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
// value that underflows is a finite number as well. Only a token that
// may overflow has to be converted to find out.
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
{
value_float = 0;
return true;
}
value_float = decimal_to_float<number_float_t>(s); value_float = decimal_to_float<number_float_t>(s);
return true; return true;
} }
+7
View File
@@ -250,6 +250,13 @@ class parser
// value to avoid a goto (see comment where set to true) // value to avoid a goto (see comment where set to true)
bool skip_to_state_evaluation = false; bool skip_to_state_evaluation = false;
// a document that starts with a container pushes onto the stack at
// once: allocate it in one step instead of growing it from 1 byte
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
{
states.reserve(32);
}
while (true) while (true)
{ {
if (!skip_to_state_evaluation) if (!skip_to_state_evaluation)
+46 -131
View File
@@ -275,41 +275,11 @@ class builder
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
} }
}; };
// The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows
// and at the end.
const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; arena_run(s, static_cast<std::size_t>(p - s));
const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
{ \
m_arena_write = w; \
arena_grow(n); \
w = m_arena_write; \
w_end = m_arena_end; \
}
{
const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16)
{
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, s, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, s, n);
}
w += n;
}
for (;;) for (;;)
{ {
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -317,7 +287,6 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w;
return decoded{p, start, arena_used() - start}; return decoded{p, start, arena_used() - start};
} }
if (c != '\\') if (c != '\\')
@@ -328,7 +297,7 @@ class builder
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
} }
++p; ++p;
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -346,7 +315,7 @@ class builder
{ {
return failed(error_code::string_surrogate_low, p); return failed(error_code::string_surrogate_low, p);
} }
if (end - p < 2 || p[0] != '\\' || p[1] != 'u') if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
{ {
return failed(error_code::string_surrogate_high, p); return failed(error_code::string_surrogate_high, p);
} }
@@ -362,108 +331,27 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(4);
w = put_utf8(w, cp); m_arena_write = put_utf8(m_arena_write, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(1);
*w++ = simple_escape[d]; *m_arena_write++ = simple_escape[d];
} }
else else
{ {
--p; --p;
return failed(error_code::string_escape, p); return failed(error_code::string_escape, p);
} }
#if NLOHMANN_VIEW_NEON if (p != m_end && *p == '\\')
if (p != end && *p == '\\')
{ {
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
} }
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
// as one block: where the runs between escapes are short, the vector step below would put
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
// and be slower than these steps (the scanner of the keys works the same way)
{
if (end - p >= 16)
{
const std::uint8_t* const plain = string_plain();
std::size_t steps = 16;
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP
run_found:
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps;
p += steps;
if (steps < 16)
{
if (*p >= 0x80)
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
continue;
}
}
}
#else
if (p != end && (*p == '\\' || *p == '"'))
{
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
}
#endif
#if NLOHMANN_VIEW_VECTOR
// the plain bytes up to the next quote, backslash, or control
// character are copied as they are scanned, 16 bytes at a time
for (;;)
{
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
{
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
break;
}
NLOHMANN_VIEW_ENSURE(16)
const unsigned k = copy_plain_run16(p, w);
w += k;
p += k;
if (k == 16)
{
continue;
}
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
break;
}
#else
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, m_end);
const auto n = static_cast<std::size_t>(p - r); arena_run(r, static_cast<std::size_t>(p - r));
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
#endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -509,6 +397,14 @@ run_found:
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
} }
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
{
arena_grow(n);
}
}
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
{ {
const std::size_t used = arena_used(); const std::size_t used = arena_used();
@@ -517,6 +413,25 @@ run_found:
m_arena_end = m_doc.arena.data() + m_doc.arena.size(); m_arena_end = m_doc.arena.data() + m_doc.arena.size();
} }
/// append the run [r, r + n) to the arena; short runs as one fixed-size
/// 16-byte move when both sides have the room (no library call)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
{
if (n <= 16 && m_end - r >= 16)
{
arena_ensure(16);
std::memcpy(m_arena_write, r, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(n);
std::memcpy(m_arena_write, r, n);
}
m_arena_write += n;
}
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after /// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
/// it. (A returned pointer stays in a register; writing through a /// it. (A returned pointer stays in a register; writing through a
/// reference to m_arena_write would store and reload it around every byte.) /// reference to m_arena_write would store and reload it around every byte.)
@@ -1039,11 +954,11 @@ indent_done:
++p; ++p;
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (p != e && *p == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9')) else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
{ {
p = skip_digits(p + 1, e); p = skip_digits(p + 1, e);
} }
@@ -1054,7 +969,7 @@ indent_done:
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
bool is_float = false; bool is_float = false;
if (cur() == '.') if (p != e && *p == '.')
{ {
++p; ++p;
const unsigned char* const f0 = p; const unsigned char* const f0 = p;
@@ -1067,20 +982,20 @@ indent_done:
is_float = true; is_float = true;
} }
std::int64_t exponent = 0; std::int64_t exponent = 0;
if ((cur() | 0x20) == 'e') if (p != e && (*p | 0x20) == 'e')
{ {
++p; ++p;
bool exp_negative = false; bool exp_negative = false;
if (cur() == '+' || cur() == '-') if (p != e && (*p == '+' || *p == '-'))
{ {
exp_negative = *p == '-'; exp_negative = *p == '-';
++p; ++p;
} }
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur()))) if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
{ {
return fail(error_code::number_after_exponent); return fail(error_code::number_after_exponent);
} }
while (is_digit(cur())) while (p != e && is_digit(*p))
{ {
if (exponent < 100000) if (exponent < 100000)
{ {
@@ -1142,7 +1057,7 @@ indent_done:
++p; // opening quote ++p; // opening quote
const unsigned char* const s = p; const unsigned char* const s = p;
p = scan_string_run<Value>(p, e); p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(cur() == '"')) if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{ {
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s)); push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
++p; ++p;
-34
View File
@@ -10,9 +10,6 @@
#include <algorithm> // max #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#ifndef JSON_NO_IO
#include <cstdio> // FILE, fread
#endif
#include <istream> // istream, streambuf #include <istream> // istream, streambuf
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
@@ -157,37 +154,6 @@ inline void append_stream(std::string& buf, std::istream& is)
is.clear(is.rdstate() | std::ios::eofbit); is.clear(is.rdstate() | std::ios::eofbit);
} }
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
template<typename T>
struct is_c_stream
#ifndef JSON_NO_IO
: std::is_same<T, std::FILE*>
#else
: std::false_type
#endif
{};
#ifndef JSON_NO_IO
/// append the rest of a C stream: in blocks with fread, not character by
/// character (the library's file adapter reads with fgetc);
/// f is not null
inline void append_file(std::string& buf, std::FILE* f)
{
for (;;)
{
const std::size_t used = buf.size();
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
buf.resize(used + room);
const std::size_t got = std::fread(&buf[used], 1, room, f);
buf.resize(used + got);
if (got < room)
{
break; // end of file, or an error (as fgetc, which returns EOF for both)
}
}
}
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+8 -10
View File
@@ -147,21 +147,20 @@ class view_item
view_item(const iterator& it, std::size_t index) view_item(const iterator& it, std::size_t index)
: m_it(it) : m_it(it)
, m_position(index) {
{} if (!it.is_object_iterator())
{
m_index = std::to_string(index);
}
}
/// the member key, or the element index for arrays (as text made on the /// the member key, or the element index for arrays
/// first call, so that iterating over the values pays nothing for it)
string_view_t key() const string_view_t key() const
{ {
if (m_it.is_object_iterator()) if (m_it.is_object_iterator())
{ {
return m_it.key(); return m_it.key();
} }
if (m_index.empty())
{
m_index = std::to_string(m_position);
}
return string_view_t(m_index.data(), m_index.size()); return string_view_t(m_index.data(), m_index.size());
} }
@@ -184,8 +183,7 @@ class view_item
private: private:
iterator m_it; iterator m_it;
std::size_t m_position; ///< the index of an array element std::string m_index{}; // NOLINT(readability-redundant-member-init)
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
}; };
/// the range returned by basic_json_view::items() /// the range returned by basic_json_view::items()
-8
View File
@@ -166,10 +166,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
{ {
return e + idx; // a growable block: one link per element return e + idx; // a growable block: one link per element
} }
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
{
return e + idx; // no element has a subtree: the elements are the nodes after the array
}
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
e = document_data::after(e); e = document_data::after(e);
@@ -185,10 +181,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0; const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
{
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
}
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
{ {
last = c; last = c;
+6 -44
View File
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <array> // array #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t #include <cstdint> // int64_t, uint8_t
#include <string> // string #include <string> // string
@@ -44,9 +44,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
using nav = navigation<Editable>; using nav = navigation<Editable>;
// the open containers: their next element (or key of the next member), // the open containers: their next element (or key of the next member),
// their end, and whether they are objects; for the first 16 levels on the // their end, and whether they are objects; sized once from the
// stack, else sized once from the document's deepest nesting (for a // document's deepest nesting (for a subtree an upper bound)
// subtree an upper bound)
struct frame struct frame
{ {
const node* pos; const node* pos;
@@ -54,43 +53,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
bool object; bool object;
}; };
// a scalar needs neither the handler nor the stack of open containers
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
return BasicJsonType(string_t(d.str(*n), n->len));
case value_t::number_integer:
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
case value_t::number_unsigned:
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
case value_t::number_float:
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
case value_t::boolean:
return BasicJsonType((n->flags & node_flags::is_true) != 0);
case value_t::null:
return BasicJsonType(nullptr);
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
BasicJsonType result; BasicJsonType result;
sax_t sax(result, true); sax_t sax(result, true);
const string_t no_token{}; const string_t no_token{};
// (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
std::vector<frame> deep{};
frame* open = shallow.data();
std::size_t capacity = shallow.size();
if (d.max_depth > capacity)
{
deep.resize(d.max_depth);
open = deep.data();
capacity = deep.size();
}
std::size_t depth = 0; std::size_t depth = 0;
for (;;) for (;;)
{ {
@@ -110,13 +76,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
{ {
sax.start_array(n->len); sax.start_array(n->len);
} }
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
{ {
// (deeper than the recorded nesting, e.g. after edits) open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
deep.assign(open, open + depth);
deep.resize(depth * 2);
open = deep.data();
capacity = deep.size();
} }
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
break; break;
+11 -101
View File
@@ -47,96 +47,27 @@ constexpr std::size_t index_max_displacement = 64;
static_assert(document_data::index_min_members >= 2 * index_max_displacement, static_assert(document_data::index_min_members >= 2 * index_max_displacement,
"a table must be much larger than the probe window of a lookup"); "a table must be much larger than the probe window of a lookup");
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the /// hash of a key: its bytes, eight at a time, in a fixed byte order
/// last word may overlap the one before (so that no loop over single bytes is
/// needed); a key of fewer than eight bytes is read as two overlapping words of
/// four or two bytes (in the native byte order: the hash is not stored, and the
/// same on both sides of a lookup)
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
{ {
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1); std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
while (n >= 8)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
n -= 8;
}
std::uint64_t w = 0; std::uint64_t w = 0;
if (n >= 8) for (std::size_t i = 0; i < n; ++i)
{ {
const unsigned char* const last = p + n - 8; w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
while (p < last)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
}
w = read_eight_bytes(last);
} }
else if (n >= 4) h = (h ^ w) * 0x94D049BB133111EBu;
{
std::uint32_t head = 0;
std::uint32_t tail = 0;
std::memcpy(&head, p, 4);
std::memcpy(&tail, p + n - 4, 4);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
}
else if (n >= 2)
{
std::uint16_t head = 0;
std::uint16_t tail = 0;
std::memcpy(&head, p, 2);
std::memcpy(&tail, p + n - 2, 2);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
}
else if (n == 1)
{
w = p[0];
}
// (a product only carries bits upwards, and the slot is taken from the low
// bits: the high bits of the last word are folded down before the multiplication)
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
return h ^ (h >> 31u); return h ^ (h >> 31u);
} }
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
/// (the first and the last 8, 4, or 2 bytes) and without a call
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
{
if (n >= 8)
{
std::uint64_t x0 = 0;
std::uint64_t x1 = 0;
std::uint64_t y0 = 0;
std::uint64_t y1 = 0;
std::memcpy(&x0, a, 8);
std::memcpy(&x1, a + n - 8, 8);
std::memcpy(&y0, b, 8);
std::memcpy(&y1, b + n - 8, 8);
return x0 == y0 && x1 == y1;
}
if (n >= 4)
{
std::uint32_t x0 = 0;
std::uint32_t x1 = 0;
std::uint32_t y0 = 0;
std::uint32_t y1 = 0;
std::memcpy(&x0, a, 4);
std::memcpy(&x1, a + n - 4, 4);
std::memcpy(&y0, b, 4);
std::memcpy(&y1, b + n - 4, 4);
return x0 == y0 && x1 == y1;
}
if (n >= 2)
{
std::uint16_t x0 = 0;
std::uint16_t x1 = 0;
std::uint16_t y0 = 0;
std::uint16_t y1 = 0;
std::memcpy(&x0, a, 2);
std::memcpy(&x1, a + n - 2, 2);
std::memcpy(&y0, b, 2);
std::memcpy(&y1, b + n - 2, 2);
return x0 == y0 && x1 == y1;
}
return n == 0 || *a == *b;
}
/// build the table of a large object /// build the table of a large object
inline void build_object_index(document_data& d, node* obj) inline void build_object_index(document_data& d, node* obj)
{ {
@@ -201,27 +132,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const document_data::object_index& ix = d.indexes[obj->extra - 1u];
const std::uint32_t* const table = d.index_slots.data() + ix.start; const std::uint32_t* const table = d.index_slots.data() + ix.start;
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask; std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
{
// (up to 16 bytes, the bytes are compared as two words, without a call)
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
if (s == 0)
{
return nullptr;
}
const node* const k = obj + s;
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{
return k;
}
slot = (slot + 1) & ix.mask;
}
return nullptr;
}
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{ {
const std::uint32_t s = table[slot]; const std::uint32_t s = table[slot];
-28
View File
@@ -116,34 +116,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
} }
return p; return p;
} }
/*!
@brief copy 16 bytes from p to w, and find the first quote, backslash,
control character, or non-ASCII byte among them
The bytes are copied whether or not they are all plain (the caller has room
for 16 bytes and keeps only the plain ones).
@return the offset of that byte, or 16 if there is none
*/
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
#endif
}
#endif #endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH #if NLOHMANN_VIEW_SSSE3_DISPATCH
+4 -2
View File
@@ -161,6 +161,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_parser; friend class ::nlohmann::detail::json_sax_dom_parser;
template<typename BasicJsonType, typename InputAdapterType> template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
template<typename BasicJsonType, typename InputAdapterType>
friend class ::nlohmann::detail::json_sax_dom_callback_parser; friend class ::nlohmann::detail::json_sax_dom_callback_parser;
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
friend struct ::nlohmann::detail::diagnostic_positions; friend struct ::nlohmann::detail::diagnostic_positions;
@@ -6287,7 +6289,7 @@ public:
from_ubjson/from_bjdata/from_bon8/from_bson overloads from_ubjson/from_bjdata/from_bon8/from_bson overloads
Building the @ref binary_reader as a named local, rather than as a Building the @ref binary_reader as a named local, rather than as a
temporary that @ref detail::json_sax_dom_parser::parse is called on in temporary that @ref detail::json_sax_dom_binary_parser::parse is called on in
the same expression, avoids a false-positive cppcheck accessMoved the same expression, avoids a false-positive cppcheck accessMoved
warning in each of the 16 callers. warning in each of the 16 callers.
*/ */
@@ -6298,7 +6300,7 @@ public:
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{ {
basic_json result; basic_json result;
detail::json_sax_dom_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions); detail::json_sax_dom_binary_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler); binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
if (!reader.sax_parse(&sdp, strict, tag_handler)) if (!reader.sax_parse(&sdp, strict, tag_handler))
{ {
+10 -67
View File
@@ -295,7 +295,7 @@ class basic_json_view
} }
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? element(idx) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
/// any other integer type (int, unsigned, long, std::int64_t, ...; a /// any other integer type (int, unsigned, long, std::int64_t, ...; a
@@ -358,7 +358,7 @@ class basic_json_view
{ {
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return element(idx); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
/// any other integer type, see operator[]; negative values are out of /// any other integer type, see operator[]; negative values are out of
@@ -392,13 +392,7 @@ class basic_json_view
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) return value(key, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the value a JSON pointer refers to converted to T, or the default /// the value a JSON pointer refers to converted to T, or the default
@@ -417,12 +411,7 @@ class basic_json_view
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) return value(ptr, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the first element or member value; a primitive value itself. Throws /// the first element or member value; a primitive value itself. Throws
@@ -444,8 +433,7 @@ class basic_json_view
{ {
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -462,8 +450,7 @@ class basic_json_view
{ {
return end(); return end();
} }
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? iterator(m_doc, k, true) : end(); return k != nullptr ? iterator(m_doc, k, true) : end();
} }
@@ -480,8 +467,7 @@ class basic_json_view
/// whether this is an object with a member with this key /// whether this is an object with a member with this key
bool contains(string_view_t key) const bool contains(string_view_t key) const
{ {
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
} }
bool contains(const char* key) const bool contains(const char* key) const
@@ -641,14 +627,7 @@ class basic_json_view
// the compact writer keeps 64 bytes of slack, so that it does not grow // the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end // the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160; const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
if (unedited()) detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
{
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
}
else
{
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
}
return out; return out;
} }
@@ -728,7 +707,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -806,32 +785,10 @@ class basic_json_view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
if (unedited())
{
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
/// read-only document (nothing is moved or linked), which is cheaper
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
{
return !navigation::editable() || m_doc->edits == nullptr;
}
/// the element of an array at an index below its size
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
{
if (unedited())
{
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
}
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
// --- get() dispatch --- // --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
@@ -1571,8 +1528,7 @@ class basic_json_document
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0 append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
: std::is_base_of<std::istream, D>::value ? 1 : std::is_base_of<std::istream, D>::value ? 1
: detail::view::is_adapter_input<D>::value ? 2 : detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
} }
@@ -1594,19 +1550,6 @@ class basic_json_document
detail::view::append_adapter(buf, std::move(input.ia)); detail::view::append_adapter(buf, std::move(input.ia));
} }
#ifndef JSON_NO_IO
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
{
if (file == nullptr)
{
// (as before: the library's adapter decides what a null stream yields)
detail::view::append_adapter(buf, detail::input_adapter(file));
return;
}
detail::view::append_file(buf, file);
}
#endif
template<typename T> template<typename T>
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/) static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
{ {
+1094 -64
View File
File diff suppressed because it is too large. Load diff
+81 -423
View File
@@ -756,34 +756,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
} }
return p; return p;
} }
/*!
@brief copy 16 bytes from p to w, and find the first quote, backslash,
control character, or non-ASCII byte among them
The bytes are copied whether or not they are all plain (the caller has room
for 16 bytes and keeps only the plain ones).
@return the offset of that byte, or 16 if there is none
*/
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
#endif
}
#endif #endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH #if NLOHMANN_VIEW_SSSE3_DISPATCH
@@ -1465,41 +1437,11 @@ class builder
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
} }
}; };
// The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows
// and at the end.
const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; arena_run(s, static_cast<std::size_t>(p - s));
const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) \
{ \
m_arena_write = w; \
arena_grow(n); \
w = m_arena_write; \
w_end = m_arena_end; \
}
{
const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16)
{
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, s, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, s, n);
}
w += n;
}
for (;;) for (;;)
{ {
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -1507,7 +1449,6 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w;
return decoded{p, start, arena_used() - start}; return decoded{p, start, arena_used() - start};
} }
if (c != '\\') if (c != '\\')
@@ -1518,7 +1459,7 @@ class builder
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p); return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
} }
++p; ++p;
if (p == end) if (p == m_end)
{ {
return failed(error_code::string_missing_quote, p); return failed(error_code::string_missing_quote, p);
} }
@@ -1536,7 +1477,7 @@ class builder
{ {
return failed(error_code::string_surrogate_low, p); return failed(error_code::string_surrogate_low, p);
} }
if (end - p < 2 || p[0] != '\\' || p[1] != 'u') if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
{ {
return failed(error_code::string_surrogate_high, p); return failed(error_code::string_surrogate_high, p);
} }
@@ -1552,108 +1493,27 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(4);
w = put_utf8(w, cp); m_arena_write = put_utf8(m_arena_write, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(1);
*w++ = simple_escape[d]; *m_arena_write++ = simple_escape[d];
} }
else else
{ {
--p; --p;
return failed(error_code::string_escape, p); return failed(error_code::string_escape, p);
} }
#if NLOHMANN_VIEW_NEON if (p != m_end && *p == '\\')
if (p != end && *p == '\\')
{ {
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
} }
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
// as one block: where the runs between escapes are short, the vector step below would put
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
// and be slower than these steps (the scanner of the keys works the same way)
{
if (end - p >= 16)
{
const std::uint8_t* const plain = string_plain();
std::size_t steps = 16;
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP
run_found:
NLOHMANN_VIEW_ENSURE(16)
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps;
p += steps;
if (steps < 16)
{
if (*p >= 0x80)
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
continue;
}
}
}
#else
if (p != end && (*p == '\\' || *p == '"'))
{
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
}
#endif
#if NLOHMANN_VIEW_VECTOR
// the plain bytes up to the next quote, backslash, or control
// character are copied as they are scanned, 16 bytes at a time
for (;;)
{
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
{
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
break;
}
NLOHMANN_VIEW_ENSURE(16)
const unsigned k = copy_plain_run16(p, w);
w += k;
p += k;
if (k == 16)
{
continue;
}
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
}
break;
}
#else
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, m_end);
const auto n = static_cast<std::size_t>(p - r); arena_run(r, static_cast<std::size_t>(p - r));
NLOHMANN_VIEW_ENSURE(n)
std::memcpy(w, r, n);
w += n;
#endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -1699,6 +1559,14 @@ run_found:
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
} }
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
{
arena_grow(n);
}
}
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n) NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
{ {
const std::size_t used = arena_used(); const std::size_t used = arena_used();
@@ -1707,6 +1575,25 @@ run_found:
m_arena_end = m_doc.arena.data() + m_doc.arena.size(); m_arena_end = m_doc.arena.data() + m_doc.arena.size();
} }
/// append the run [r, r + n) to the arena; short runs as one fixed-size
/// 16-byte move when both sides have the room (no library call)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
{
if (n <= 16 && m_end - r >= 16)
{
arena_ensure(16);
std::memcpy(m_arena_write, r, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(n);
std::memcpy(m_arena_write, r, n);
}
m_arena_write += n;
}
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after /// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
/// it. (A returned pointer stays in a register; writing through a /// it. (A returned pointer stays in a register; writing through a
/// reference to m_arena_write would store and reload it around every byte.) /// reference to m_arena_write would store and reload it around every byte.)
@@ -2229,11 +2116,11 @@ indent_done:
++p; ++p;
} }
const unsigned char* const int_start = p; const unsigned char* const int_start = p;
if (cur() == '0') if (p != e && *p == '0')
{ {
++p; ++p;
} }
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9')) else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
{ {
p = skip_digits(p + 1, e); p = skip_digits(p + 1, e);
} }
@@ -2244,7 +2131,7 @@ indent_done:
const auto int_digits = static_cast<std::size_t>(p - int_start); const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0; std::size_t frac_digits = 0;
bool is_float = false; bool is_float = false;
if (cur() == '.') if (p != e && *p == '.')
{ {
++p; ++p;
const unsigned char* const f0 = p; const unsigned char* const f0 = p;
@@ -2257,20 +2144,20 @@ indent_done:
is_float = true; is_float = true;
} }
std::int64_t exponent = 0; std::int64_t exponent = 0;
if ((cur() | 0x20) == 'e') if (p != e && (*p | 0x20) == 'e')
{ {
++p; ++p;
bool exp_negative = false; bool exp_negative = false;
if (cur() == '+' || cur() == '-') if (p != e && (*p == '+' || *p == '-'))
{ {
exp_negative = *p == '-'; exp_negative = *p == '-';
++p; ++p;
} }
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur()))) if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
{ {
return fail(error_code::number_after_exponent); return fail(error_code::number_after_exponent);
} }
while (is_digit(cur())) while (p != e && is_digit(*p))
{ {
if (exponent < 100000) if (exponent < 100000)
{ {
@@ -2332,7 +2219,7 @@ indent_done:
++p; // opening quote ++p; // opening quote
const unsigned char* const s = p; const unsigned char* const s = p;
p = scan_string_run<Value>(p, e); p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(cur() == '"')) if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{ {
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s)); push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
++p; ++p;
@@ -3281,96 +3168,27 @@ constexpr std::size_t index_max_displacement = 64;
static_assert(document_data::index_min_members >= 2 * index_max_displacement, static_assert(document_data::index_min_members >= 2 * index_max_displacement,
"a table must be much larger than the probe window of a lookup"); "a table must be much larger than the probe window of a lookup");
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the /// hash of a key: its bytes, eight at a time, in a fixed byte order
/// last word may overlap the one before (so that no loop over single bytes is
/// needed); a key of fewer than eight bytes is read as two overlapping words of
/// four or two bytes (in the native byte order: the hash is not stored, and the
/// same on both sides of a lookup)
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
{ {
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1); std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
while (n >= 8)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
n -= 8;
}
std::uint64_t w = 0; std::uint64_t w = 0;
if (n >= 8) for (std::size_t i = 0; i < n; ++i)
{ {
const unsigned char* const last = p + n - 8; w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
while (p < last)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
}
w = read_eight_bytes(last);
} }
else if (n >= 4) h = (h ^ w) * 0x94D049BB133111EBu;
{
std::uint32_t head = 0;
std::uint32_t tail = 0;
std::memcpy(&head, p, 4);
std::memcpy(&tail, p + n - 4, 4);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
}
else if (n >= 2)
{
std::uint16_t head = 0;
std::uint16_t tail = 0;
std::memcpy(&head, p, 2);
std::memcpy(&tail, p + n - 2, 2);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
}
else if (n == 1)
{
w = p[0];
}
// (a product only carries bits upwards, and the slot is taken from the low
// bits: the high bits of the last word are folded down before the multiplication)
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
return h ^ (h >> 31u); return h ^ (h >> 31u);
} }
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
/// (the first and the last 8, 4, or 2 bytes) and without a call
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
{
if (n >= 8)
{
std::uint64_t x0 = 0;
std::uint64_t x1 = 0;
std::uint64_t y0 = 0;
std::uint64_t y1 = 0;
std::memcpy(&x0, a, 8);
std::memcpy(&x1, a + n - 8, 8);
std::memcpy(&y0, b, 8);
std::memcpy(&y1, b + n - 8, 8);
return x0 == y0 && x1 == y1;
}
if (n >= 4)
{
std::uint32_t x0 = 0;
std::uint32_t x1 = 0;
std::uint32_t y0 = 0;
std::uint32_t y1 = 0;
std::memcpy(&x0, a, 4);
std::memcpy(&x1, a + n - 4, 4);
std::memcpy(&y0, b, 4);
std::memcpy(&y1, b + n - 4, 4);
return x0 == y0 && x1 == y1;
}
if (n >= 2)
{
std::uint16_t x0 = 0;
std::uint16_t x1 = 0;
std::uint16_t y0 = 0;
std::uint16_t y1 = 0;
std::memcpy(&x0, a, 2);
std::memcpy(&x1, a + n - 2, 2);
std::memcpy(&y0, b, 2);
std::memcpy(&y1, b + n - 2, 2);
return x0 == y0 && x1 == y1;
}
return n == 0 || *a == *b;
}
/// build the table of a large object /// build the table of a large object
inline void build_object_index(document_data& d, node* obj) inline void build_object_index(document_data& d, node* obj)
{ {
@@ -3435,27 +3253,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
const document_data::object_index& ix = d.indexes[obj->extra - 1u]; const document_data::object_index& ix = d.indexes[obj->extra - 1u];
const std::uint32_t* const table = d.index_slots.data() + ix.start; const std::uint32_t* const table = d.index_slots.data() + ix.start;
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask; std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
{
// (up to 16 bytes, the bytes are compared as two words, without a call)
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
if (s == 0)
{
return nullptr;
}
const node* const k = obj + s;
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{
return k;
}
slot = (slot + 1) & ix.mask;
}
return nullptr;
}
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance) for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{ {
const std::uint32_t s = table[slot]; const std::uint32_t s = table[slot];
@@ -3624,10 +3421,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
{ {
return e + idx; // a growable block: one link per element return e + idx; // a growable block: one link per element
} }
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
{
return e + idx; // no element has a subtree: the elements are the nodes after the array
}
for (std::size_t i = 0; i < idx; ++i) for (std::size_t i = 0; i < idx; ++i)
{ {
e = document_data::after(e); e = document_data::after(e);
@@ -3643,10 +3436,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0; const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container); const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container); const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
{
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
}
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset)) for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
{ {
last = c; last = c;
@@ -5512,9 +5301,6 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // max #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#ifndef JSON_NO_IO
#include <cstdio> // FILE, fread
#endif
#include <istream> // istream, streambuf #include <istream> // istream, streambuf
#include <string> // basic_string, char_traits, string #include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference #include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
@@ -5660,37 +5446,6 @@ inline void append_stream(std::string& buf, std::istream& is)
is.clear(is.rdstate() | std::ios::eofbit); is.clear(is.rdstate() | std::ios::eofbit);
} }
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
template<typename T>
struct is_c_stream
#ifndef JSON_NO_IO
: std::is_same<T, std::FILE*>
#else
: std::false_type
#endif
{};
#ifndef JSON_NO_IO
/// append the rest of a C stream: in blocks with fread, not character by
/// character (the library's file adapter reads with fgetc);
/// f is not null
inline void append_file(std::string& buf, std::FILE* f)
{
for (;;)
{
const std::size_t used = buf.size();
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
buf.resize(used + room);
const std::size_t got = std::fread(&buf[used], 1, room, f);
buf.resize(used + got);
if (got < room)
{
break; // end of file, or an error (as fgetc, which returns EOF for both)
}
}
}
#endif
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
@@ -5849,21 +5604,20 @@ class view_item
view_item(const iterator& it, std::size_t index) view_item(const iterator& it, std::size_t index)
: m_it(it) : m_it(it)
, m_position(index) {
{} if (!it.is_object_iterator())
{
m_index = std::to_string(index);
}
}
/// the member key, or the element index for arrays (as text made on the /// the member key, or the element index for arrays
/// first call, so that iterating over the values pays nothing for it)
string_view_t key() const string_view_t key() const
{ {
if (m_it.is_object_iterator()) if (m_it.is_object_iterator())
{ {
return m_it.key(); return m_it.key();
} }
if (m_index.empty())
{
m_index = std::to_string(m_position);
}
return string_view_t(m_index.data(), m_index.size()); return string_view_t(m_index.data(), m_index.size());
} }
@@ -5886,8 +5640,7 @@ class view_item
private: private:
iterator m_it; iterator m_it;
std::size_t m_position; ///< the index of an array element std::string m_index{}; // NOLINT(readability-redundant-member-init)
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
}; };
/// the range returned by basic_json_view::items() /// the range returned by basic_json_view::items()
@@ -5981,7 +5734,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <array> // array #include <algorithm> // max
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t #include <cstdint> // int64_t, uint8_t
#include <string> // string #include <string> // string
@@ -6021,9 +5774,8 @@ BasicJsonType materialize(const document_data& d, const node* n)
using nav = navigation<Editable>; using nav = navigation<Editable>;
// the open containers: their next element (or key of the next member), // the open containers: their next element (or key of the next member),
// their end, and whether they are objects; for the first 16 levels on the // their end, and whether they are objects; sized once from the
// stack, else sized once from the document's deepest nesting (for a // document's deepest nesting (for a subtree an upper bound)
// subtree an upper bound)
struct frame struct frame
{ {
const node* pos; const node* pos;
@@ -6031,43 +5783,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
bool object; bool object;
}; };
// a scalar needs neither the handler nor the stack of open containers
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
return BasicJsonType(string_t(d.str(*n), n->len));
case value_t::number_integer:
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
case value_t::number_unsigned:
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
case value_t::number_float:
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
case value_t::boolean:
return BasicJsonType((n->flags & node_flags::is_true) != 0);
case value_t::null:
return BasicJsonType(nullptr);
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
BasicJsonType result; BasicJsonType result;
sax_t sax(result, true); sax_t sax(result, true);
const string_t no_token{}; const string_t no_token{};
// (shallow subtrees on the stack: a call for a small value then allocates only for the value) std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
std::vector<frame> deep{};
frame* open = shallow.data();
std::size_t capacity = shallow.size();
if (d.max_depth > capacity)
{
deep.resize(d.max_depth);
open = deep.data();
capacity = deep.size();
}
std::size_t depth = 0; std::size_t depth = 0;
for (;;) for (;;)
{ {
@@ -6087,13 +5806,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
{ {
sax.start_array(n->len); sax.start_array(n->len);
} }
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity)) if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
{ {
// (deeper than the recorded nesting, e.g. after edits) open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
deep.assign(open, open + depth);
deep.resize(depth * 2);
open = deep.data();
capacity = deep.size();
} }
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object}; open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
break; break;
@@ -7875,7 +7590,7 @@ class basic_json_view
} }
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
} }
return idx < m_node->len ? element(idx) : basic_json_view(); return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
} }
/// any other integer type (int, unsigned, long, std::int64_t, ...; a /// any other integer type (int, unsigned, long, std::int64_t, ...; a
@@ -7938,7 +7653,7 @@ class basic_json_view
{ {
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range")); detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
} }
return element(idx); return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
} }
/// any other integer type, see operator[]; negative values are out of /// any other integer type, see operator[]; negative values are out of
@@ -7972,13 +7687,7 @@ class basic_json_view
string_t value(string_view_t key, const char* default_value) const string_t value(string_view_t key, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) return value(key, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the value a JSON pointer refers to converted to T, or the default /// the value a JSON pointer refers to converted to T, or the default
@@ -7997,12 +7706,7 @@ class basic_json_view
string_t value(const json_pointer& ptr, const char* default_value) const string_t value(const json_pointer& ptr, const char* default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) return value(ptr, string_t(default_value));
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
} }
/// the first element or member value; a primitive value itself. Throws /// the first element or member value; a primitive value itself. Throws
@@ -8024,8 +7728,7 @@ class basic_json_view
{ {
if (is_structured() && m_node->len != 0) if (is_structured() && m_node->len != 0)
{ {
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0)) return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
} }
return front(); return front();
} }
@@ -8042,8 +7745,7 @@ class basic_json_view
{ {
return end(); return end();
} }
const node* const k = unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? iterator(m_doc, k, true) : end(); return k != nullptr ? iterator(m_doc, k, true) : end();
} }
@@ -8060,8 +7762,7 @@ class basic_json_view
/// whether this is an object with a member with this key /// whether this is an object with a member with this key
bool contains(string_view_t key) const bool contains(string_view_t key) const
{ {
return is_object() && (unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size()) != nullptr return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
: detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr);
} }
bool contains(const char* key) const bool contains(const char* key) const
@@ -8221,14 +7922,7 @@ class basic_json_view
// the compact writer keeps 64 bytes of slack, so that it does not grow // the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end // the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160; const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
if (unedited()) detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
{
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
}
else
{
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
}
return out; return out;
} }
@@ -8308,7 +8002,7 @@ class basic_json_view
{ {
return BasicJsonType(value_t::discarded); return BasicJsonType(value_t::discarded);
} }
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node); return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
} }
/// byte offset of this value in the source text (for strings: of the /// byte offset of this value in the source text (for strings: of the
@@ -8386,32 +8080,10 @@ class basic_json_view
/// (object required) /// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{ {
if (unedited())
{
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()); const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view(); return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
} }
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
/// read-only document (nothing is moved or linked), which is cheaper
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
{
return !navigation::editable() || m_doc->edits == nullptr;
}
/// the element of an array at an index below its size
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
{
if (unedited())
{
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
}
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
// --- get() dispatch --- // --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
@@ -9151,8 +8823,7 @@ class basic_json_document
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11 // NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0 append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
: std::is_base_of<std::istream, D>::value ? 1 : std::is_base_of<std::istream, D>::value ? 1
: detail::view::is_adapter_input<D>::value ? 2 : detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
// NOLINTEND(readability-avoid-nested-conditional-operator) // NOLINTEND(readability-avoid-nested-conditional-operator)
} }
@@ -9174,19 +8845,6 @@ class basic_json_document
detail::view::append_adapter(buf, std::move(input.ia)); detail::view::append_adapter(buf, std::move(input.ia));
} }
#ifndef JSON_NO_IO
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
{
if (file == nullptr)
{
// (as before: the library's adapter decides what a null stream yields)
detail::view::append_adapter(buf, detail::input_adapter(file));
return;
}
detail::view::append_file(buf, file);
}
#endif
template<typename T> template<typename T>
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/) static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
{ {
+29
View File
@@ -2952,3 +2952,32 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
} }
} }
#endif #endif
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
{
// accept() does not convert a float that cannot overflow; the tokens
// around the limit (and beyond it) must still be rejected exactly as
// parse() rejects them
const char* const numbers[] =
{
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
"0.00000000000000000001e328", "0.00000000000000000001e329"
};
for (const char* number : numbers)
{
for (const std::string text :
{
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
})
{
INFO(text);
const bool parses = !json::parse(text, nullptr, false).is_discarded();
CHECK(json::accept(text) == parses);
}
}
}
-123
View File
@@ -185,29 +185,6 @@ TEST_CASE("json_view")
// duplicate keys: the last value, at the position of the first key // duplicate keys: the last value, at the position of the first key
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})")); CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})"); CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
// scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local
for (const char* scalar :
{"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", "\"\\u00e4\\n\""
})
{
CAPTURE(scalar)
CHECK(materialized<json_document>(scalar) == json::parse(scalar));
const std::string in_array = std::string("[1,") + scalar + "]";
const json_document d = json_document::parse(in_array);
CHECK(d.root()[1].materialize() == json::parse(scalar));
CHECK(d.root()[1].get<json>() == json::parse(scalar));
}
for (const std::size_t levels :
{
std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33}
})
{
CAPTURE(levels)
const std::string nested = std::string(levels, '[') + "{\"k\":[1,{\"j\":2}]}" + std::string(levels, ']');
CHECK(materialized<json_document>(nested) == json::parse(nested));
const std::string wide = "{\"a\":" + nested + ",\"b\":" + nested + "}";
CHECK(materialized<json_document>(wide) == json::parse(wide));
}
// very deep nesting (iterative, as parse()) // very deep nesting (iterative, as parse())
const std::string deep = std::string(100000, '[') + std::string(100000, ']'); const std::string deep = std::string(100000, '[') + std::string(100000, ']');
// (one check per step, so that a failure shows which step it is) // (one check per step, so that a failure shows which step it is)
@@ -433,31 +410,6 @@ TEST_CASE("json_view")
std::istringstream rvalue_source("[1]"); std::istringstream rvalue_source("[1]");
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source()); CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
// a C stream is read in blocks as well (more than one block, and a
// text whose length is a multiple of the block size)
for (const std::size_t extra :
{
std::size_t{0}, std::size_t{1}
})
{
std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
if (long_file != nullptr)
{
std::string padded = text;
padded.append((4096 - (padded.size() % 4096)) % 4096 + extra, ' ');
CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size());
CHECK(std::fseek(long_file, 0, SEEK_SET) == 0);
const json_document from_long_file = json_document::parse(long_file);
CHECK(from_long_file.source() == padded);
CHECK(from_long_file.root().materialize() == json::parse(text));
CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory)
}
}
{
json_document from_null;
CHECK_THROWS_AS(from_null = json_document::parse(static_cast<std::FILE*>(nullptr)), json::parse_error&);
}
// a stream without a buffer is an empty input // a stream without a buffer is an empty input
std::istream no_buffer(nullptr); std::istream no_buffer(nullptr);
CHECK(json_document::parse(no_buffer, false).is_discarded()); CHECK(json_document::parse(no_buffer, false).is_discarded());
@@ -1054,49 +1006,6 @@ TEST_CASE("json_view element access and iteration")
#endif #endif
} }
SECTION("arrays whose elements have no subtrees: index and back()")
{
// (the elements are adjacent in the index; an array, an object, or an
// empty container anywhere makes the lookup skip element by element)
const std::vector<std::string> texts =
{
"[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]",
"[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", "[\"a\\u00e4\",\"b\"]"
};
for (const std::string& text : texts)
{
CAPTURE(text)
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json expected = json::parse(text);
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(v[i].materialize() == expected[i]);
CHECK(v.at(i).materialize() == expected[i]);
}
CHECK(v[expected.size()].is_discarded());
if (!expected.empty())
{
CHECK(v.back().materialize() == expected.back());
}
}
// a long array (the direct lookup is not limited to short ones), also with a container at its end
std::string text = "[0";
for (int i = 1; i < 1000; ++i)
{
text += "," + std::to_string(i);
}
const json_document scalars = json_document::parse(text + "]");
CHECK(scalars.root()[999].get<int>() == 999);
CHECK(scalars.root()[500].get<int>() == 500);
CHECK(scalars.root().at(1).get<int>() == 1);
CHECK(scalars.root().back().get<int>() == 999);
const json_document container_last = json_document::parse(text + ",[1000]]");
CHECK(container_last.root()[999].get<int>() == 999);
CHECK(container_last.root()[1000][0].get<int>() == 1000);
CHECK(container_last.root().back()[0].get<int>() == 1000);
}
SECTION("iterators") SECTION("iterators")
{ {
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})"); const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
@@ -1136,28 +1045,6 @@ TEST_CASE("json_view element access and iteration")
CHECK((*first++).key() == "0"); CHECK((*first++).key() == "0");
CHECK((*first).key() == "1"); CHECK((*first).key() == "1");
CHECK(++first == items.end()); CHECK(++first == items.end());
// the keys of array elements are their indexes, whether or not they are asked for
std::string numbers = "[0";
for (int i = 1; i < 1234; ++i)
{
numbers += "," + std::to_string(i);
}
const json_document array = json_document::parse(numbers + "]");
std::size_t position = 0;
for (const auto& item : array.root().items())
{
CHECK(item.value().get<std::size_t>() == position);
if (position % 3 != 0) // (not for every item: values alone do not make the key)
{
CHECK(item.key() == std::to_string(position));
CHECK(item.key().data() == item.key().data()); // (the same text every time)
const json_view::item copy = item;
CHECK(copy.key() == std::to_string(position));
}
++position;
}
CHECK(position == 1234);
#ifdef JSON_HAS_CPP_17 #ifdef JSON_HAS_CPP_17
std::string pairs; std::string pairs;
for (const auto [key, value] : d.root().items()) for (const auto [key, value] : d.root().items())
@@ -1496,17 +1383,7 @@ TEST_CASE("json_view values")
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); }))); CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); }))); CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); }))); CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
// string defaults: the member is not a string, or the value is neither an object nor an array
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
#endif #endif
// (defaults and members longer than the short string optimization)
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
CHECK(v.value("missing", "a default longer than fifteen characters") == "a default longer than fifteen characters");
CHECK(v.value(json::json_pointer("/s"), "a default longer than fifteen characters") == "text");
CHECK(v.value(json::json_pointer("/zz"), "a default longer than fifteen characters") == "a default longer than fifteen characters");
} }
} }
-52
View File
@@ -523,58 +523,6 @@ TEST_CASE("json_view builder: strings across vector blocks")
} }
} }
TEST_CASE("json_view builder: escapes between runs of text")
{
// The text between escapes is copied 16 bytes at a time where there are
// that many bytes left, and with the UTF-8 check where there is
// non-ASCII text. Runs of every length from 0 to 40 between valid and
// invalid escapes, UTF-8 sequences, and control characters, in strings
// that are cut at all places near the end of the text.
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
const std::array<const char*, 9> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\", "\x01"}};
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
std::mt19937 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t run = 0; run <= 40; ++run)
{
for (int i = 0; i < 60; ++i)
{
std::string content = std::string(rng() % 20, 'a');
const auto pieces = 1 + (rng() % 6);
for (unsigned k = 0; k < pieces; ++k)
{
content += valid[rng() % valid.size()];
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
switch (rng() % 8)
{
case 0:
content += utf8[rng() % 3];
break;
case 1:
content += utf8[rng() % utf8.size()];
break;
case 2:
content += invalid[rng() % invalid.size()];
break;
default:
break;
}
}
check_string(content);
check_string(content.substr(0, content.size() - (rng() % 4)));
}
}
// the text before the first escape, and runs and escapes at the end of the text
for (std::size_t before = 0; before <= 40; ++before)
{
for (const char* escape : valid)
{
check_string(std::string(before, 'a') + escape);
check_string(std::string(before, 'a') + escape + std::string(before % 19, 'z'));
check_string(std::string(before, 'a') + escape + escape + std::string(before % 19, 'z'));
}
}
}
TEST_CASE("json_view node integer bits") TEST_CASE("json_view node integer bits")
{ {
using nlohmann::detail::view::integer_bits; using nlohmann::detail::view::integer_bits;
-138
View File
@@ -21,7 +21,6 @@ using nlohmann::ordered_json_view;
using json_view_test::generator; using json_view_test::generator;
using json_view_test::has_duplicate_keys; using json_view_test::has_duplicate_keys;
#include <algorithm>
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
@@ -30,7 +29,6 @@ using json_view_test::has_duplicate_keys;
#include <iomanip> #include <iomanip>
#include <map> #include <map>
#include <random> #include <random>
#include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -600,142 +598,6 @@ TEST_CASE("json_view large objects")
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t))); CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
} }
SECTION("keys that differ in their last bytes are spread over the table")
{
// The hash must spread the usual families of keys (numbered names, hex
// strings, dates, short names): a key farther than index_max_displacement
// from its home slot would leave the object without a table. The keys
// are placed with linear probing, as the table does.
const auto max_displacement = [](const std::vector<std::string>& keys)
{
std::size_t slots = 16;
while (slots < 2 * keys.size())
{
slots *= 2;
}
std::vector<bool> used(slots, false);
std::size_t farthest = 0;
for (const std::string& key : keys)
{
std::size_t i = static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
std::size_t distance = 0;
while (used[i])
{
i = (i + 1) & (slots - 1);
++distance;
}
used[i] = true;
farthest = (std::max)(farthest, distance);
}
return farthest;
};
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
// (a format string that is not a literal is a Flawfinder error)
const auto numbered = [](const char* format, int count, int step)
{
const std::string pattern(format);
const std::size_t percent = pattern.find('%');
std::size_t conversion = percent + 1;
std::size_t width = 0;
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
{
width = width * 10 + static_cast<std::size_t>(pattern[conversion] - '0');
++conversion;
}
const bool hex = pattern[conversion] == 'x';
std::vector<std::string> keys;
for (int i = 0; i < count; ++i)
{
std::ostringstream key;
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
<< (hex ? std::hex : std::dec) << i * step << pattern.substr(conversion + 1);
keys.push_back(key.str());
}
return keys;
};
for (const char* format :
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
})
{
CAPTURE(format)
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement);
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
}
std::mt19937_64 rng(7);
for (const std::size_t length :
{
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
})
{
CAPTURE(length)
std::set<std::string> distinct;
while (distinct.size() < (length == 1 ? 26 : (length == 2 ? 500 : 700))) // (there are 26 and 676 keys of one and two letters)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
distinct.insert(key);
}
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
}
}
SECTION("keys of every length that differ in one byte")
{
// In a large object, a key is found by comparing its bytes with those of the member the
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
std::mt19937_64 rng(2026);
std::vector<std::string> keys;
std::set<std::string> distinct;
for (std::size_t length = 0; length <= 40; ++length)
{
for (int copy = 0; copy < 5; ++copy)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
if (distinct.insert(key).second)
{
keys.push_back(key);
}
}
}
std::string text = "{";
for (std::size_t i = 0; i < keys.size(); ++i)
{
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
}
text += "}";
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
const json_document d = json_document::parse(text);
const json_view v = d.root();
for (std::size_t i = 0; i < keys.size(); ++i)
{
CAPTURE(keys[i])
CHECK(v[keys[i]].get<std::size_t>() == i);
CHECK(v.at(keys[i]).get<std::size_t>() == i);
CHECK(v.contains(keys[i]));
for (std::size_t at = 0; at < keys[i].size(); ++at)
{
std::string other = keys[i];
other[at] = other[at] == 'z' ? 'y' : 'z';
CAPTURE(other)
CHECK(v.contains(other) == (distinct.count(other) != 0));
}
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
if (!keys[i].empty())
{
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
}
}
}
SECTION("shrink_to_fit releases the tables' spare capacity") SECTION("shrink_to_fit releases the tables' spare capacity")
{ {
const auto make_text = [](int objects, int members) const auto make_text = [](int objects, int members)
-109
View File
@@ -661,115 +661,6 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
CHECK(d.root().materialize() == expected); CHECK(d.root().materialize() == expected);
} }
TEST_CASE("json_view edits: arrays whose elements have no subtrees")
{
// such an array is indexed directly while it is in its parsed layout; edits
// that replace elements by scalars keep it, others move it
json_editable_document d = json_editable_document::parse("[10,20,30,40]");
json expected = json::array({10, 20, 30, 40});
const auto check = [&]
{
CHECK(d.root().size() == expected.size());
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(d.root()[i].materialize() == expected[i]);
CHECK(d.root().at(i).materialize() == expected[i]);
}
CHECK(d.root()[expected.size()].is_discarded());
CHECK(d.root().back().materialize() == expected.back());
CHECK(d.root().dump() == expected.dump());
};
check();
d.set(d.root()[1], "x");
expected[1] = "x";
check();
d.set(d.root()[3], 2.5);
expected[3] = 2.5;
check();
d.set(d.root()[2], json::array({1, 2}));
expected[2] = json::array({1, 2});
check();
d.push_back(d.root(), 50);
expected.push_back(50);
check();
d.insert(d.root(), 0, true);
expected.insert(expected.begin(), true);
check();
d.erase(d.root(), 1);
expected.erase(expected.begin() + 1);
check();
d.set(d.root()[2], nullptr);
expected[2] = nullptr;
check();
}
TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed")
{
// the stack of open containers of materialize() is sized from the nesting of the parsed text;
// a value added by an edit can be deeper than that (and than the stack that is local to the call)
json_editable_document d = json_editable_document::parse("[1,[2]]");
json expected = json::array({1, json::array({2})});
json nested = 3;
for (int level = 0; level < 40; ++level)
{
nested = json::array({nested, json{{"k", level}}});
}
d.push_back(d.root(), nested);
expected.push_back(nested);
CHECK(d.root().materialize() == expected);
CHECK(d.root()[2].materialize() == nested);
d.set(d.root()[1][0], nested);
expected[1][0] = nested;
CHECK(d.root().materialize() == expected);
CHECK(d.root().get<json>() == expected);
}
TEST_CASE("json_view edits: an editable document before and after its first edit")
{
// until the first edit, an editable document is read like a read-only one; views made before the edit
// keep working after it
const std::string text = R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"text","f":[]})";
const json_document read_only = json_document::parse(text);
json_editable_document d = json_editable_document::parse(text);
const auto root = d.root();
const auto same_as_read_only = [&]
{
CHECK(root.dump() == read_only.root().dump());
CHECK(root.dump(2) == read_only.root().dump(2));
CHECK(root.materialize() == read_only.root().materialize());
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].at(3).get<int>() == 4);
CHECK(root["a"].back().get<int>() == 4);
CHECK(root.contains("b"));
CHECK(!root.contains("zz"));
CHECK(root.find("e")->get_string() == "text");
CHECK(root.find("zz") == root.end());
CHECK(root["b"]["c"].get<bool>());
CHECK(root.value("e", "none") == "text");
std::size_t count = 0;
for (const auto m : root["a"])
{
CHECK(m.materialize() == read_only.root()["a"][count].materialize());
++count;
}
CHECK(count == 4);
};
same_as_read_only();
// an edit that changes a scalar in place, then one that moves an array
d.set(root["e"], "other");
CHECK(root["e"].get_string() == "other");
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].back().get<int>() == 4);
d.push_back(root["f"], 7);
CHECK(root["f"][0].get<int>() == 7);
CHECK(root["f"].back().get<int>() == 7);
CHECK(root.dump() == R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"other","f":[7]})");
d.set(root["a"], 2, json::array({1, 2}));
CHECK(root["a"][2][1].get<int>() == 2);
CHECK(root["a"].dump() == "[1,2,[1,2],4]");
CHECK(root.materialize() == json::parse(R"({"a":[1,2,[1,2],4],"b":{"c":true,"d":null},"e":"other","f":[7]})"));
}
namespace namespace
{ {
// reads of "a" in an object that repeats it: the first member wins, however the // reads of "a" in an object that repeats it: the first member wins, however the
+271
View File
@@ -237,3 +237,274 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
CHECK(oj.dump() == "{\"a\":1}"); CHECK(oj.dump() == "{\"a\":1}");
} }
} }
namespace
{
// the sizes around which the DOM parser changes how it stores the elements of
// an array (16) and the members of an object in a vector (32, and 64 for the
// key index)
const std::size_t container_sizes[] = {0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200};
template<typename Json>
std::string array_text(const std::size_t n, const std::size_t depth)
{
std::string text = "[";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
// alternate scalars, empty containers and nested containers
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += "\"s" + std::to_string(i) + "\"";
break;
case 2:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
default:
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
break;
}
}
return text + "]";
}
template<typename Json>
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
{
std::string text = "{";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"key" + std::to_string(i) + "\":";
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
case 2:
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
break;
default:
text += "\"value\"";
break;
}
}
if (duplicates)
{
// keys that are there already, the first and the last one, with other values
text += n == 0 ? "" : ",";
text += "\"key0\":[1,2],\"key" + std::to_string(n - 1) + "\":{\"again\":true}";
}
return text + "}";
}
template<typename Json>
void check_same_dump(const std::string& text)
{
// the text is what dump() writes for the parsed value, and the value
// survives a second round trip: arrays keep their elements in order, an
// ordered_json keeps its members in order of appearance
const Json parsed = Json::parse(text);
const Json again = Json::parse(parsed.dump());
CHECK(parsed == again);
CHECK(parsed.dump() == again.dump());
CHECK(Json::accept(text));
}
} // namespace
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
{
SECTION("arrays")
{
for (const std::size_t n : container_sizes)
{
const std::string text = array_text<json>(n, 2);
INFO("size ", n);
const json parsed = json::parse(text);
CHECK(parsed.is_array());
CHECK(parsed.size() == n);
for (std::size_t i = 0; i < n; i += 4)
{
CHECK(parsed[i] == i);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
CHECK(ordered_json::parse(text).dump() == parsed.dump());
}
}
SECTION("objects")
{
for (const bool duplicates :
{
false, true
})
{
for (const std::size_t n : container_sizes)
{
const std::string text = object_text<json>(n, 2, duplicates);
INFO("size ", n, " duplicates ", duplicates);
const json parsed = json::parse(text);
const ordered_json ordered = ordered_json::parse(text);
CHECK(parsed.is_object());
CHECK(ordered.is_object());
CHECK(parsed.size() == ordered.size());
// the same members, in the order of their first appearance
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
if (i < n)
{
CHECK(it.key() == "key" + std::to_string(i));
}
CHECK(parsed.at(it.key()) == json(it.value()));
}
if (duplicates && n > 1)
{
// the later value replaced the earlier one in its place
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
CHECK(ordered.begin().key() == "key0");
CHECK(ordered.size() == n);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
}
}
}
SECTION("objects with many different and repeated keys")
{
// 300 members of which a third repeat an earlier key
std::string text = "{";
for (std::size_t i = 0; i < 300; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
}
text += "}";
const ordered_json ordered = ordered_json::parse(text);
const json parsed = json::parse(text);
CHECK(ordered.size() == 200);
CHECK(parsed.size() == 200);
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
CHECK(it.key() == "k" + std::to_string(i));
// the last value written
CHECK(it.value() == (i < 100 ? i + 200 : i));
CHECK(parsed.at(it.key()) == json(it.value()));
}
}
}
namespace
{
// a deterministic source of structure for parsing tests: builds a JSON text
// and, at the same time, the value it has to parse to
class tree_generator
{
public:
explicit tree_generator(std::uint32_t seed) : state(seed) {}
template<typename Json>
Json make(std::string& text, const std::size_t depth)
{
switch (next(depth == 0 ? 5 : 9))
{
case 0:
text += "null";
return Json(nullptr);
case 1:
text += "true";
return Json(true);
case 2:
{
const int n = static_cast<int>(next(2000)) - 1000;
text += std::to_string(n);
return Json(n);
}
case 3:
text += "\"text\"";
return Json("text");
case 4:
text += "2.5";
return Json(2.5);
case 5:
case 6:
case 7:
{
Json array = Json::array();
text += "[";
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
array.push_back(make<Json>(text, depth - 1));
}
text += "]";
return array;
}
default:
{
Json object = Json::object();
text += "{";
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
const std::string key = "k" + std::to_string(i);
text += (i == 0 ? "\"" : ",\"") + key + "\":";
object[key] = make<Json>(text, depth - 1);
}
text += "}";
return object;
}
}
}
private:
std::uint32_t next(const std::uint32_t bound)
{
state = state * 1664525U + 1013904223U;
return (state >> 8) % bound;
}
std::size_t size()
{
return container_sizes[next(sizeof(container_sizes) / sizeof(*container_sizes))];
}
std::uint32_t state;
};
} // namespace
TEST_CASE("parsing random trees of arrays and objects")
{
for (std::uint32_t seed = 1; seed <= 150; ++seed)
{
INFO("seed ", seed);
std::string text;
std::string ordered_text;
const json expected = tree_generator(seed).make<json>(text, 3);
const ordered_json ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
CHECK(text == ordered_text);
CHECK(json::parse(text) == expected);
CHECK(json::accept(text));
const ordered_json ordered = ordered_json::parse(text);
CHECK(ordered == ordered_expected);
CHECK(ordered.dump() == ordered_expected.dump());
CHECK(json::parse(text).dump() == expected.dump());
}
}