mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 14:58:54 +00:00
json_view: faster reading, indexing and C-stream parsing (#5888)
* Read C streams into a json_document in blocks json_document::parse(FILE*) (and read(), accept(), parse_copy()) went through the library's file input adapter and appended the text character by character with fgetc. Read it with fread in blocks instead, like the std::istream overload already does. Parsing citm_catalog.json (1.7 MB) from a FILE*: Apple M1: 80.6 M -> 8.4 M instructions, 4.8 ms -> 0.55 ms (parse of the same text from a std::string: 8.7 M) Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 47.0 M -> 13.7 M / 45.8 M -> 14.2 M The parsed documents and the error cases are identical (checked over the test corpus and 30 mutants of each file). A null FILE* is handled as before (by the library's adapter). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Index arrays without subtrees directly operator[](idx), at(idx) and back() skipped the elements of an array one after the other, whatever they were. In an array none of whose elements is an array or object (numbers, strings, booleans, null) the elements are adjacent nodes, which the array node's own sizes tell: its subtree has exactly one node more than it has elements. Such an element is now the node idx after the array node; any other array is walked as before. Reading r[i] for every i of an array of 3000 integers: Apple M1: 28.8 M instructions, 3.1 ms -> 0.1 M instructions, 4 us Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 84.1 M -> 3.1 M / 75.0 M -> 3.0 M Arrays that contain arrays are unchanged. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Skip the bounds checks of number and string tokens in NUL-terminated input For a NUL-terminated input (std::string, C string, string literal), the parser reads the terminator instead of comparing p with the end, via cursor::cur(). Number and string scanning still tested `p != e` before each look at the next byte (after the integer digits for '.', for 'e', after the exponent sign and digits, and after a string for its closing quote), although the terminator can never be one of those characters. They now use cur(), which is the plain load for NUL-terminated input and the bounds check for ptr+len input, as before. Parsing, Apple M1 instructions per parse: twitter 3.126 M -> 3.089 M (-1.2 %), citm_catalog 8.70 M -> 8.58 M (-1.3 %), jeopardy 306.1 M -> 302.5 M (-1.2 %), signed integers 281 M -> 266 M (-5.4 %), canada and floats -0.1 % / -0.9 %. Linux x86-64 Ir, GCC 13 / Clang 18: twitter -0.5 % / -0.0 %, signed integers +0.2 % / -0.7 %. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the index text of items() on arrays lazy basic_json_view::items() on an array formatted every element's index with std::to_string when the item was made, whether or not the loop asks for the key. The item now keeps the position and makes the text on the first key() call. Objects are not affected. Iterating over the items of an array of 3000 numbers and reading only the values: Apple M1: 25.5 us -> 4.6 us (0.45 M -> ~0 net instructions) Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 14.9 M -> 4.1 M / 15.3 M -> 4.6 M Reading key() costs what it did (24.5 us). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the default of value() with a C string only if the member is missing basic_json_view::value(key, "text") and value(pointer, "text") built a std::string from the default first and passed it on, so a call allocated for the default even when the member exists, and copied it again when it does not. They now look the member up and make the result directly: from the member if there is one, else from the C string. Behavior, including the exceptions, is the same. Two calls (member found, member missing) with a 40 character default, Apple M1: 1.6 K -> 0.7 K instructions Linux x86-64 Ir (500 rounds), GCC 13 / Clang 18: 2.46 M -> 2.22 M / 2.40 M -> 2.15 M Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Materialize scalars directly and keep the stack of small subtrees local basic_json_view::materialize() (and get<BasicJsonType>()) always set up the SAX handler that parse() uses and a std::vector for the open containers, sized from the document's deepest nesting. For a scalar, which is most of what is materialized one value at a time, neither is needed: the value is made directly (the same constructors the handler ends up calling). For arrays and objects the stack of open containers is now a local array for the first 16 levels, and the vector is only used for deeper documents. Materializing five scalars of a small document, Apple M1: 3.6 K -> 0.9 K instructions Linux x86-64 Ir (500 rounds), GCC 13 / Clang 18: 2.96 M -> 2.37 M / 2.90 M -> 2.29 M Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Copy the text between escapes while scanning it A string that contains an escape is decoded by builder::slow_string(). After each escape it scanned the next run of plain bytes (8 or 16 byte-by-byte steps, then a vector loop), and then copied that run to the decode arena in a second pass. The run is now copied 16 bytes at a time while it is scanned (one vector load, a store of the same 16 bytes, and the mask of the bytes that end the run), and the write position lives in a local variable, not in the builder, since a store of a character may alias the builder's members and made the compiler reload it after every escape. A run that ends at a quote needs no scan, and non-ASCII text goes through the UTF-8 check as before; the portable code is unchanged. Parsing, Apple M1 instructions: jeopardy.json 304.0 M -> 293.8 M (-3.4 %), 3000 strings with escapes 1.72 M -> 1.54 M (-10 %), twitter -0.6 %. Linux x86-64 Ir, GCC 13 / Clang 18: escaped strings -7.6 % / -11.4 %, twitter 0.0 % / 0.0 %. Documents and error messages are identical on 900 k random strings of escapes, UTF-8 and control characters (also under ASan), and on mutants of the test corpus. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Hash the last bytes of a key of a large object without a loop key_hash() mixed a key eight bytes at a time and then packed the remaining 0-7 bytes one by one, in a loop. The last word of a key of eight or more bytes is now read as the last eight bytes of the key (overlapping the word before), and a key of 2-7 bytes as two overlapping words of four or two bytes. A product only moves bits upwards, and the slot is taken from the low bits: the high bits of the last word are folded down before the multiplication, since keys that differ only in their last characters ("user_17", "user_18") would otherwise share slots. The hash is not stored, so its values may change. Looking up every key of an object of 1000 random keys, Apple M1 instructions per 1000 lookups: keys of 5 bytes 175 K -> 93 K, hex strings of 8 bytes 177 K -> 168 K, keys of 12 bytes 196 K -> 186 K Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 5-byte keys -4.7 % / -8.8 %, 12-byte keys -3.3 % / -7.4 %. Building the tables while parsing such objects is cheaper as well (5-byte keys: 13.3 us -> 10.1 us). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Compare short keys of a large object without a call of memcmp find_indexed() compared the key of a member the table points to with memcmp, a library call for any length. Up to 16 bytes, the bytes are now compared as two overlapping words (the first and the last 8, 4, or 2 bytes), as find_member() already does for the objects without a table; longer keys still use memcmp. Looking up every key of an object of 1000 random keys, Apple M1 instructions per 1000 lookups (with the previous commit): keys of 5 bytes 93 K -> 72 K, hex strings of 8 bytes 168 K -> 148 K, keys of 12 bytes 186 K -> 170 K, keys of 24 bytes 198 K -> 193 K (unchanged, memcmp). Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 5-byte keys -7.2 % / -7.3 %, 12-byte keys -5.9 % / -6.6 %. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Read an unedited editable document like a read-only one Views of a json_editable_document followed edits at every step: the lookup, the element access, back(), dump() and materialize() were built for nodes that can be moved or linked, though no edit changes the index until the first one (every edit creates the edit state before it touches a node). Until then these now take the read-only code: one check of the document's edit state per call, not per member or per node. After the first edit nothing changes. Views that were made earlier keep working, as the check is made when they are used. Unedited editable document, Apple M1: dump() of twitter.json 1.40 M -> 1.22 M instructions (-12.6 %); lookups unchanged. Linux x86-64 Ir, GCC 13 / Clang 18: dump -1.7 % / -1.0 %, lookups -0.1 % / 0.0 %. Walking with iterators is unchanged, and so is dump() after an edit. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Format the numbered test keys of json_view's index with a stream The keys were made with snprintf() and a format from a list, which Flawfinder reports as an error (CWE-134, a format string that is not a literal). A stream now formats them; the 30000 keys of the test are the same as before. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Put the key number of the json_view index test in parentheses astyle formatted i * step after << as a pointer declaration (i* step), which failed the amalgamation check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the compiler and lint findings of json_view's read side and tests - find() and contains() dispatch on Editable for the member lookup; for documents that cannot be edited both branches were the same call (GCC -Wduplicated-branches) - json_view.hpp includes <cstdio> for the FILE overload (cpplint) - the arena check macro of the builder is on one line (cpplint read it as an if body without braces) - tests: no nested conditional (also MSVC C4018), no useless cast, parentheses around mixed operators, raw string literals, += instead of temporary concatenations, and NOLINT for the fixed seeds and a find() != npos, as elsewhere in the tests Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Grow the decode arena through a function and store its position back on every return slow_string() kept its write position in locals and grew the arena through the macro NLOHMANN_VIEW_ENSURE. The macro is now the always-inline member arena_ensure() (a lambda is not inlined at every call site by clang, which then keeps the position in memory: +11 % instructions on escape-heavy strings), and a guard stores the position back into m_arena_write on every return instead of by hand on the successful one. Instruction counts are unchanged (clang) or 1 % lower (GCC). Also word the complexity of back() as that of at(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
1 parent
e9c4fe3cdf
commit
dd7686a269
16 files changed
+1386
-167
No files matched your search
@@ -275,11 +275,34 @@ 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
|
||||
}
|
||||
};
|
||||
// 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 by `write_back` on every return.
|
||||
const unsigned char* const end = m_end;
|
||||
const std::size_t start = arena_used();
|
||||
arena_run(s, static_cast<std::size_t>(p - s));
|
||||
char* w = m_arena_write;
|
||||
const char* w_end = m_arena_end;
|
||||
const arena_write_back write_back(*this, w);
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(p - s);
|
||||
if (n <= 16 && end - s >= 16)
|
||||
{
|
||||
arena_ensure(w, w_end, 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)
|
||||
arena_ensure(w, w_end, n);
|
||||
std::memcpy(w, s, n);
|
||||
}
|
||||
w += n;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
if (p == m_end)
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -287,7 +310,9 @@ class builder
|
||||
if (c == '"')
|
||||
{
|
||||
++p;
|
||||
return decoded{p, start, arena_used() - start};
|
||||
// (from w: `write_back` stores it only after the return value
|
||||
// is made; the arena is allocated, since an escape was written)
|
||||
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
|
||||
}
|
||||
if (c != '\\')
|
||||
{
|
||||
@@ -297,7 +322,7 @@ class builder
|
||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||
}
|
||||
++p;
|
||||
if (p == m_end)
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
@@ -315,7 +340,7 @@ class builder
|
||||
{
|
||||
return failed(error_code::string_surrogate_low, p);
|
||||
}
|
||||
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
@@ -331,26 +356,106 @@ class builder
|
||||
}
|
||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||
}
|
||||
arena_ensure(4);
|
||||
m_arena_write = put_utf8(m_arena_write, cp);
|
||||
arena_ensure(w, w_end, 4);
|
||||
w = put_utf8(w, cp);
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||
{
|
||||
arena_ensure(1);
|
||||
*m_arena_write++ = simple_escape[d];
|
||||
arena_ensure(w, w_end, 1);
|
||||
*w++ = simple_escape[d];
|
||||
}
|
||||
else
|
||||
{
|
||||
--p;
|
||||
return failed(error_code::string_escape, p);
|
||||
}
|
||||
if (p != m_end && *p == '\\')
|
||||
#if NLOHMANN_VIEW_NEON
|
||||
if (p != end && *p == '\\')
|
||||
{
|
||||
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:
|
||||
arena_ensure(w, w_end, 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);
|
||||
arena_ensure(w, w_end, 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);
|
||||
arena_ensure(w, w_end, n);
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
break;
|
||||
}
|
||||
arena_ensure(w, w_end, 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);
|
||||
arena_ensure(w, w_end, n);
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#else
|
||||
const unsigned char* const r = p;
|
||||
p = scan_string_run(p, m_end);
|
||||
arena_run(r, static_cast<std::size_t>(p - r));
|
||||
p = scan_string_run(p, end);
|
||||
const auto n = static_cast<std::size_t>(p - r);
|
||||
arena_ensure(w, w_end, n);
|
||||
std::memcpy(w, r, n);
|
||||
w += n;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,19 +497,49 @@ class builder
|
||||
char* m_arena_write = nullptr;
|
||||
char* m_arena_end = nullptr;
|
||||
|
||||
/// room for n more bytes at w, the write position kept in a local, with
|
||||
/// w_end the end of the arena. (A lambda instead is not always inlined,
|
||||
/// and then w would be kept in memory, as m_arena_write is.)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// stores a write position kept in a local back into m_arena_write when
|
||||
/// the function that keeps it returns, on every path
|
||||
class arena_write_back
|
||||
{
|
||||
public:
|
||||
arena_write_back(builder& b, char*& w) noexcept
|
||||
: m_builder(b)
|
||||
, m_w(w)
|
||||
{}
|
||||
~arena_write_back()
|
||||
{
|
||||
m_builder.m_arena_write = m_w;
|
||||
}
|
||||
|
||||
arena_write_back(const arena_write_back&) = delete;
|
||||
arena_write_back& operator=(const arena_write_back&) = delete;
|
||||
arena_write_back(arena_write_back&&) = delete;
|
||||
arena_write_back& operator=(arena_write_back&&) = delete;
|
||||
|
||||
private:
|
||||
builder& m_builder;
|
||||
char*& m_w;
|
||||
};
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
|
||||
{
|
||||
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)
|
||||
{
|
||||
const std::size_t used = arena_used();
|
||||
@@ -413,25 +548,6 @@ class builder
|
||||
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
|
||||
/// it. (A returned pointer stays in a register; writing through a
|
||||
/// reference to m_arena_write would store and reload it around every byte.)
|
||||
@@ -954,11 +1070,11 @@ indent_done:
|
||||
++p;
|
||||
}
|
||||
const unsigned char* const int_start = p;
|
||||
if (p != e && *p == '0')
|
||||
if (cur() == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
{
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
@@ -969,7 +1085,7 @@ indent_done:
|
||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||
std::size_t frac_digits = 0;
|
||||
bool is_float = false;
|
||||
if (p != e && *p == '.')
|
||||
if (cur() == '.')
|
||||
{
|
||||
++p;
|
||||
const unsigned char* const f0 = p;
|
||||
@@ -982,20 +1098,20 @@ indent_done:
|
||||
is_float = true;
|
||||
}
|
||||
std::int64_t exponent = 0;
|
||||
if (p != e && (*p | 0x20) == 'e')
|
||||
if ((cur() | 0x20) == 'e')
|
||||
{
|
||||
++p;
|
||||
bool exp_negative = false;
|
||||
if (p != e && (*p == '+' || *p == '-'))
|
||||
if (cur() == '+' || cur() == '-')
|
||||
{
|
||||
exp_negative = *p == '-';
|
||||
++p;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
||||
{
|
||||
return fail(error_code::number_after_exponent);
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
while (is_digit(cur()))
|
||||
{
|
||||
if (exponent < 100000)
|
||||
{
|
||||
@@ -1057,7 +1173,7 @@ indent_done:
|
||||
++p; // opening quote
|
||||
const unsigned char* const s = p;
|
||||
p = scan_string_run<Value>(p, e);
|
||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
||||
{
|
||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||
++p;
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <cstddef> // size_t
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE, fread
|
||||
#endif
|
||||
#include <istream> // istream, streambuf
|
||||
#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
|
||||
@@ -154,6 +157,37 @@ inline void append_stream(std::string& buf, std::istream& is)
|
||||
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 detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -147,20 +147,21 @@ class view_item
|
||||
|
||||
view_item(const iterator& it, std::size_t index)
|
||||
: m_it(it)
|
||||
{
|
||||
if (!it.is_object_iterator())
|
||||
{
|
||||
m_index = std::to_string(index);
|
||||
}
|
||||
}
|
||||
, m_position(index)
|
||||
{}
|
||||
|
||||
/// the member key, or the element index for arrays
|
||||
/// the member key, or the element index for arrays (as text made on the
|
||||
/// first call, so that iterating over the values pays nothing for it)
|
||||
string_view_t key() const
|
||||
{
|
||||
if (m_it.is_object_iterator())
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -183,7 +184,8 @@ class view_item
|
||||
|
||||
private:
|
||||
iterator m_it;
|
||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
||||
std::size_t m_position; ///< the index of an array element
|
||||
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()
|
||||
|
||||
@@ -166,6 +166,10 @@ 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
|
||||
}
|
||||
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)
|
||||
{
|
||||
e = document_data::after(e);
|
||||
@@ -181,6 +185,10 @@ 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 node* const end = navigation<Editable>::end(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))
|
||||
{
|
||||
last = c;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t
|
||||
#include <string> // string
|
||||
@@ -44,8 +44,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
// their end, and whether they are objects; sized once from the
|
||||
// document's deepest nesting (for a subtree an upper bound)
|
||||
// their end, and whether they are objects; for the first 16 levels on the
|
||||
// stack, else sized once from the document's deepest nesting (for a
|
||||
// subtree an upper bound)
|
||||
struct frame
|
||||
{
|
||||
const node* pos;
|
||||
@@ -53,10 +54,43 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
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;
|
||||
sax_t sax(result, true);
|
||||
const string_t no_token{};
|
||||
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||
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;
|
||||
for (;;)
|
||||
{
|
||||
@@ -76,9 +110,13 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
sax.start_array(n->len);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
||||
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
||||
{
|
||||
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
||||
// (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};
|
||||
break;
|
||||
|
||||
@@ -47,27 +47,96 @@ constexpr std::size_t index_max_displacement = 64;
|
||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||
"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
|
||||
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
||||
/// 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
|
||||
{
|
||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||
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;
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
if (n >= 8)
|
||||
{
|
||||
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
||||
const unsigned char* const last = p + n - 8;
|
||||
while (p < last)
|
||||
{
|
||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||
h ^= h >> 29u;
|
||||
p += 8;
|
||||
}
|
||||
w = read_eight_bytes(last);
|
||||
}
|
||||
h = (h ^ w) * 0x94D049BB133111EBu;
|
||||
else if (n >= 4)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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
|
||||
inline void build_object_index(document_data& d, node* obj)
|
||||
{
|
||||
@@ -132,6 +201,27 @@ 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 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;
|
||||
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)
|
||||
{
|
||||
const std::uint32_t s = table[slot];
|
||||
|
||||
@@ -116,6 +116,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
||||
}
|
||||
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
|
||||
|
||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <array> // array
|
||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstdio> // FILE
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <istream> // istream // IWYU pragma: keep
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
@@ -295,7 +296,7 @@ class basic_json_view
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
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();
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
}
|
||||
|
||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||
@@ -358,7 +359,7 @@ class basic_json_view
|
||||
{
|
||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||
return element(idx);
|
||||
}
|
||||
|
||||
/// any other integer type, see operator[]; negative values are out of
|
||||
@@ -392,7 +393,13 @@ class basic_json_view
|
||||
|
||||
string_t value(string_view_t key, const char* default_value) const
|
||||
{
|
||||
return value(key, string_t(default_value));
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
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
|
||||
@@ -411,7 +418,12 @@ class basic_json_view
|
||||
|
||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||
{
|
||||
return value(ptr, string_t(default_value));
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
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
|
||||
@@ -433,7 +445,8 @@ class basic_json_view
|
||||
{
|
||||
if (is_structured() && m_node->len != 0)
|
||||
{
|
||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*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();
|
||||
}
|
||||
@@ -450,7 +463,7 @@ class basic_json_view
|
||||
{
|
||||
return end();
|
||||
}
|
||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
||||
const node* const k = find_key(key);
|
||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||
}
|
||||
|
||||
@@ -467,7 +480,7 @@ class basic_json_view
|
||||
/// whether this is an object with a member with this key
|
||||
bool contains(string_view_t key) const
|
||||
{
|
||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
||||
return is_object() && find_key(key) != nullptr;
|
||||
}
|
||||
|
||||
bool contains(const char* key) const
|
||||
@@ -627,7 +640,14 @@ class basic_json_view
|
||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||
// the buffer just before the end
|
||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
||||
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
|
||||
if (unedited())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -707,7 +727,7 @@ class basic_json_view
|
||||
{
|
||||
return BasicJsonType(value_t::discarded);
|
||||
}
|
||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||
}
|
||||
|
||||
/// byte offset of this value in the source text (for strings: of the
|
||||
@@ -785,10 +805,50 @@ class basic_json_view
|
||||
/// (object required)
|
||||
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());
|
||||
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 member of this object with @a key (the last one if the key occurs more than once), or nullptr
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
|
||||
{
|
||||
return find_key(key, std::integral_constant<bool, Editable> {});
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
|
||||
{
|
||||
// (an editable document without edits is looked up as one that cannot be edited)
|
||||
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
|
||||
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
|
||||
}
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
|
||||
{
|
||||
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
|
||||
}
|
||||
|
||||
/// 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 ---
|
||||
|
||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
@@ -1528,7 +1588,8 @@ class basic_json_document
|
||||
// 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
|
||||
: std::is_base_of<std::istream, D>::value ? 1
|
||||
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
||||
: detail::view::is_adapter_input<D>::value ? 2
|
||||
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||
}
|
||||
|
||||
@@ -1550,6 +1611,19 @@ class basic_json_document
|
||||
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>
|
||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user