Compare commits

..
21 Commits
Author SHA1 Message Date
Niels Lohmann 5e13f834c1 Copy the text of float tokens only when a SAX handler asks for it (#5852)
After the digits are converted while scanning, copying the token text into
token_buffer (an out-of-line std::string::append) was about 10% of parsing
canada.json. The only reader is the string argument of
json_sax::number_float(), and the library's own handlers
(json_sax_dom_parser, json_sax_dom_callback_parser, json_sax_acceptor)
ignore it.

For contiguous input, the fast path now keeps a pointer to the token in the
input, and get_string() copies it on first use. The parser passes an empty
string to the handlers marked by the new internal trait
sax_ignores_number_text instead of calling get_string(), so json::parse()
and json::accept() never copy float text. User handlers, and
JSON_DIAGNOSTIC_POSITIONS (which reads get_string().size()), still see the
exact text. Keys and strings, which never have pending text, use the
unchecked get_string_token(). Input read through a window into a stream
(std::istream, FILE*) copies the text right away, because the next read may
refill the window.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 12:26:46 +02:00
Niels Lohmann e13e976801 Accumulate number digits while scanning them (#5851)
* Accumulate number digits while scanning them

The contiguous number fast path validated a token byte by byte, copied it
into token_buffer, and then converted it in out-of-line functions that
scanned the digits a second time: parse_float_native() called
parse_float_significand(), which returned its result through memory and
called std::any_of (out of line with GCC) twice per number, and
parse_integer_signed() divided by ten on every digit for its overflow check.

scan_number_bulk_contiguous() now accumulates the significand while it
validates the digits, eight at a time where at least eight bytes remain
(is_eight_digits() and parse_eight_digits(), after fast_float). Floats with
at most 19 digits go straight to decimal_to_float(); integers with at most
18 digits are stored without an overflow check (a narrower number type
still rejects values that do not round-trip). Longer tokens take the
existing path. has_nonzero_digit() is a plain loop and
parse_float_significand() is always inlined, which helps that path.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the digit accumulation checks without exceptions

The section compares error messages from both lexer paths and parses
out-of-range numbers, so it does not compile (the caught exception does not
exist) and would abort with JSON_NOEXCEPTION.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Share the significant-digit loop of the integer part and the fraction

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 12:26:46 +02:00
Niels Lohmann 898e6ee81d Add benchmarks for SAX parsing, value operations, JSON Pointer, and conversions (#5845)
Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:

- Add benchmarks for SAX parsing, value operations, JSON Pointer, and conversions
- Time copies together with their destruction in the benchmarks
- Time destroying the json in ToJsonStrings

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 12:25:42 +02:00
Niels Lohmann ef1cdb59ac Update performance docs for fast std::istream and FILE* parsing (#5875)
performance.md still claimed streams have no fast path (outdated since #5828).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 12:25:10 +02:00
Niels Lohmann bd5227c2ec Fix silent wrong results for custom number types (#5805)
* Fix silent wrong results for custom number types

Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:

- Fix silent wrong results for custom number types
- Fix CI: build the out_of_range.407 message from string_t and fix a link
- Fix CI: diagnostics message, clang-tidy, and MinGW section limit
- Format the out_of_range.407 number without dump()
- Move the std::deque array test out of unit-regression3.cpp
- Fix clang-tidy and clang 3.4/3.5 failures in the custom number type changes
- Fix CI: cast the 128-bit unsigned value explicitly in the integer fast path

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Name the range checks of integer_fits_max/min

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 08:36:31 +00:00
Niels Lohmann 0cbe95ffa2 Speed up writing CBOR and MessagePack, above all integer arrays (#5824)
* Speed up writing CBOR and MessagePack, above all integer arrays

Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:

- Append to the output vector through a position-tracking sink
- Write CBOR integers without branching on their sign or size
- Write MessagePack integers without branching on their sign or size
- Use std::array for the integer size class table
- Fix clang-tidy and GCC warnings in the integer array test
- Regenerate the amalgamation

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Convert the streamed bytes explicitly in the binary writer sink tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:23:48 +02:00
Niels Lohmann 08d7fbb0a1 Read FILE* input with std::fgetc under MemorySanitizer (#5874)
With glibc and optimization, <stdio.h> turns getc_unlocked into an extern
inline function that reads the stdio buffer (fp->_IO_read_ptr) directly.
glibc, which is not instrumented, fills that buffer, so MemorySanitizer
treats the bytes as uninitialized and reports a false positive when the
parsers read from a FILE* (e.g., the fuzz harness FILE* leg, OSS-Fuzz
575915982). std::fgetc is an opaque call whose result MemorySanitizer treats
as initialized.

Select JSON_UNLOCKED_FILE_IO 0 when compiled with MemorySanitizer. This is a
regression from #5828 and not part of any release.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:22:50 +02:00
Niels Lohmann 4155e2938f Write json_view's dump() without a call or conversion per token (#5633)
* Write json_view's dump() without a call or conversion per token

The default dump() (no indentation, no ensure_ascii) gets its own
writer that makes the same walk and produces the same output:

- the write position stays in a local variable instead of a
  member, so the compiler keeps it in a register across stores
  through aliasing char pointers;
- strings and number tokens are copied with fixed-size 32-byte
  moves wherever enough source bytes remain, instead of one
  memcpy call per token;
- the innermost open container lives in local variables; a stack
  that starts as a local array of 32 entries holds the rest;
- unedited documents are walked through the node array in order,
  and integer tokens are read from the source directly.

On top of that, float tokens of at most 15 significant digits are
written straight from their digits via zmij::to_shortest() and
write_shortest(), without converting to a double and back: such
decimals are farther apart than a double's rounding interval, so
the token's digits are the double's shortest digits. Tokens of
16+ digits, or edited values, still go through decimal_to_float().
The view's own NEON write_decimal() is removed in favor of the
shared writer, and the dump output now grows in 64 KiB steps
instead of being resized to its estimate at once.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix MSVC C4127 and Infer finding in the view serializer

Test the template options through enabled() so that conditions combined
with them are not constant, and mark the nav::value() null dereference
as a false positive like materialize.hpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix out-of-bounds read when dumping a float token of an unchecked image

A float node of an image loaded with image_check::bounds can hold a
token whose bytes are not digits, so its value need not have the digit
count recorded in the node. write_double_at() passed that count to
write_short_decimal(), which indexes its table of powers of ten by it:
an assertion failure in debug builds, an out-of-bounds read in release
builds. Use the counted overload only if the value has exactly that many
digits, otherwise count them.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Tidy the view serializer: doxygen block placement, enabled() for a constant

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use a named document in the dump test: root() of a temporary does not compile

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test write_short_decimal and powers_of_ten_16 against to_chars

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy 22 findings in unit-to_chars.cpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Write doubles from their bits only where double is IEEE 754 binary64

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Split the value and pointer edit tests off unit-json_view_edit.cpp

The MinGW linker of the Windows clang jobs cannot link object files with more
than 32767 sections ("relocation truncated to fit: IMAGE_REL_AMD64_REL32
against `.rdata'"). unit-json_view_edit.cpp has 35409 sections since the fast
dump instantiates more code for editable documents, so its test cases "views
and values", "deeply nested values", "pointers below a null value", and
"strings of other documents are checked" move into
unit-json_view_edit_values.cpp. The helper exception_of_call() that both
files use moves into json_view_test_helpers.hpp.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Move the dump tests of this branch to unit-json_view_dump.cpp

The dump and comparison tests moved to their own file in the dump pull
request; the tests of the fast dump() join them there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address review: one powers-of-ten table, literal sizes from the type

- dtoa_impl::powers_of_ten() (10^0 to 10^19, one entry per line)
  replaces powers_of_ten_16() and the table of int_pow10()
- put_literal() takes the length of a string literal from its type
- small_stack holds the heap vector by reference
- the growth step of the output buffer is written in hex

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Explain why the growth of the dump's stack is static

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:30 +02:00
Niels Lohmann 4971200b54 Add images of json_documents: save() and load() (#5632)
* Add images of json_documents: save() and load()

An image is a document stored so that loading it needs no
parsing: save() writes the node index, the text and the decoded
strings; the static load() reads an image written by save().

load() takes a pointer and size, a borrowed vector, or an owned
rvalue vector; the nodes are copied so they are aligned and can
be edited, while the text and decoded strings stay in the image.

image_check controls how much load() trusts the input: full
checks structure, bounds, strings and numbers, the parser's own
guarantees; bounds checks structure and bounds only; none skips
all checks, for images from a trusted source.

Layout is little-endian only ("NJVI" header, nodes, text, decoded
strings), following the idea of zero-copy formats such as
FlatBuffers and YaFF; the check follows FlatBuffers' Verifier.

New errors: parse_error.116 for a malformed image or a failed
check, type_error.320 for a discarded document or a big-endian
target.

A dedicated fuzzer and 6,000 seeded corruptions, checked under
ASan/UBSan, found and fixed two gaps: unchecked reserved header
fields, and unbounded null/boolean offsets that could make
dump() throw std::length_error.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix MSVC errors in image test and add image_check doc anchor

Move the raw string literal out of the CHECK macro (MSVC preprocessor),
cast 64-bit header fields to std::size_t (C4244 on 32-bit), and give the
image_check section in load.md a real anchor for the mkdocs strict build.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Check each distinct string and float range once in the full image check

The full check scanned the contents of every string node and every float token over its own range, so many nodes pointing to one large range made load() quadratic. The structural walk now only collects the ranges; after it, each kind is sorted and checked once per distinct range. Ranges of one kind that overlap without being identical are rejected, as save() never writes them (nodes that share a value share the whole range). The cost is linear in the size of the image plus sorting the ranges.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Adapt the image tests to named documents and last-wins lookups

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test that the targets of links never reach an image

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test lookups in loaded images with duplicate and colliding keys

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Release the list of large objects once load() has built the indexes

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test that invalid UTF-8 of a loaded image is not copied into an editable document

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document that editable documents refuse invalid UTF-8 copied from a loaded image

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Expect first-wins lookups in the image tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid useless casts of header fields in the image tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy 22 findings in unit-json_view_image.cpp and image.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix MSVC C4244 and C2017 in the image test

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the indentation of a loop in the image test

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions
- drop edit_storage.hpp, which edit.hpp includes

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Silence a clang-tidy C++17 finding in the image test

bugprone-suspicious-stringview-data-usage flags data() of the source view
under C++17, although the length is passed and checked by the REQUIRE
before it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Build the image link test without exceptions

load_result() exists only with exceptions; with JSON_NOEXCEPTION the test
loads the image with the check directly (a failed check aborts).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Follow the renames from the review of the view builder

The review of #5620 renamed document_data's tape/tape_size/tape_cap to
nodes/node_count/node_capacity and made the decode arena a
std::vector<char>; adapt the code of this change to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Rename the emit lambda in the image writer for Qt compatibility

Qt defines emit as an empty macro, so a Qt header included before
json_view.hpp broke the lambda's declaration (see #5865). The slots
variables in the image tests are renamed as well.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of json_document images

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:29 +02:00
Niels Lohmann 823eee3b59 Add editable json_documents: set, push_back, insert, and erase (#5630)
* Add editable json_documents: set, push_back, insert, and erase

basic_json_document gets a second template parameter, Editable
(false by default), plus the aliases json_editable_document,
json_editable_view, ordered_json_editable_document and
ordered_json_editable_view.

Editable documents can change values and structure without
rewriting the source text: set()/push_back() on values, keys,
array indices and JSON pointers; insert() before an array
element; erase() of an object key, array index or JSON pointer.

New values and element sequences go into edit storage that the
document owns and never moves, so views keep referring to their
value across edits and a parsed node never moves. Read-only
documents walk the plain node array and are unaffected.

Strings are checked for UTF-8 on entry, so dump() of an editable
document never throws type_error.316. Binary values cannot be
stored (type_error.319).

A seeded differential test applies random edits to an editable
document and to the equivalent ordered_json and compares both
after every step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix old clang and MSVC C4127 in editable json documents

Value-initialize the const std::less in find_parent (clang 3.4/3.6 do not
implement DR 253), and test the Editable template argument through a
function to avoid MSVC C4127.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Copy nested values into a document without recursion

Counting and copying the nodes of a view or a basic_json value into an
editable document recursed once per nesting level, so that a deeply
nested value overflowed the stack. The four functions now walk the value
with an explicit stack, as materialize() does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Do not leave a half-assigned container when set_moved() throws

assign() rewrote the kind, length and extent of a slot before set_moved()
registered its new element sequence. set_moved() can throw
(std::bad_alloc from reserving the bookkeeping vectors) for a slot that
is not moved yet, which left a container without the moved flag that
showed its old children. The bookkeeping is now reserved first
(reserve_moved()), so that nothing after the first write to the slot can
throw. block_of() reserves before it marks anything for the same reason.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Check strings copied from another document for valid UTF-8

An editable document only holds valid UTF-8, but copy_scalar() copied the
strings and keys of a view of another document unchecked. A document of
a weaker check (or a borrowed text that changed after parsing) could
therefore bring ill-formed UTF-8 into it. The copy is checked now, with
the error that dump() reports for the string; copies within the same
document stay unchecked.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Allow the edit arena to grow up to 4 GiB - 1 bytes

append_text() doubled the capacity of the arena and rejected the result
if it exceeded 4 GiB, so that an arena of more than 2 GiB could not grow
even though the 32-bit offsets of nodes address 4 GiB - 1 bytes. The
capacity is now clamped to that limit (text_capacity()), and an append
is rejected only if the bytes themselves do not fit.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Set a null value below a pointer as basic_json does

set(json_pointer, value) turned a null parent into an object for every
last reference token, so "/a/0" and "/a/-" below {"a":null} made
{"a":{"0":1}}, where basic_json's operator[](json_pointer) makes an
array. A null parent now becomes an array for "-" and for digit tokens
(filled with nulls up to the index) and an object otherwise. An invalid
index is reported before the parent changes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Replace a container by a scalar in constant time once it is linked

Replacing an array or object that spans several nodes by a scalar looks
up its parent from the root (find_parent), which is linear in the size
of the document: setting every element of a 20,000 element array took
more than half a second. The parent only has to switch to links once;
afterward the extent of the replaced value no longer matters. Nodes that
an entry of a moved sequence links to are now marked (node_flags::linked),
and assign() skips the lookup for them.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Update the json_view banner and type_error.319 for editable documents

The banner still described a read-only view, and the documentation of
type_error.319 listed set and push_back only, although insert rejects
binary values as well.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Assign the member that lookups find when set() meets duplicate keys

An editable document reads the last member of a repeated key (as the
read-only document does), but set(object, key, value) assigned the first
one: a view taken from object[key] before the call did not show the new
value. It now assigns the last member's value, keeps the key at the
position of its first occurrence (where materialize() puts it), and drops
the other members.

The seeded differential test now also edits documents whose objects
repeat keys and compares every lookup (operator[], at, find, value,
contains, count, and JSON pointers) with the parsed basic_json value.
A targeted test covers duplicates before and after edits that move the
object, in large objects with an index, in moved arrays, and in values
copied from other documents.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Assign the first member when set() meets duplicate keys again

Lookups find the first member of a repeated key again, so set(object, key,
value) assigns that member (keeping the key where it is) and drops the
others, as documented before, and a view taken from object[key] shows the
new value. This undoes the code and documentation changes of 5cbb8e6d6.
Lookups in edited objects (navigation of editable documents) stop at the
first match, like those of parsed objects.

The tests that commit added expect the first member from lookups now. In the
seeded differential test, the documents with repeated keys repeat them after
the real members; the reference holds the first members, which the edits
address and the lookups are compared with, while materialize() is compared
with what parse() makes of the document's dump.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix MSVC C4127 in the lookups of editable json documents

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy 22 findings in the json_view headers

Silence bugprone-casting-through-void for the SSE loads (a reinterpret_cast
would trip -Wcast-align=strict), use auto for a cast initialiser, add
parentheses to a mixed expression, and name bugprone-std-namespace-modification
in the NOLINTs of the tuple_size/tuple_element specialisations.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
(cherry picked from commit 0effbaa7a2)

* Fix clang-tidy 22 findings in unit-json_view_edit.cpp and edit.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep a raw string with a backslash out of a CHECK (MSVC C2017)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), and the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), plus <version> for
std::nullptr_t, and removing <cstddef> and edit_storage.hpp.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions
- drop edit_storage.hpp, which edit.hpp includes

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep the view's edit headers clean for clang-tidy in C++17 mode

clang-tidy 22 checks the headers with -std=gnu++17 as well, where it asks
for a transparent comparator and std::make_unique. The regions map uses its
default comparator, and the allocation of the edit state keeps new (the
library is C++11) with a NOLINT.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Follow the renames from the review of the view builder

The review of #5620 renamed document_data's tape/tape_size/tape_cap to
nodes/node_count/node_capacity and made the decode arena a
std::vector<char>; adapt the code of this change to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Expect count() to count every member in the edit tests

Since #5622, count() counts every member with the key, so documents
with duplicate keys no longer give 1.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Count matching keys as unsigned in the json_view edit tests

Like in the access test, adding the int result of the conditional to a
std::size_t fails -Werror=sign-conversion with GCC.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of erase() in editable json documents

From gregmarr's review of #5630:

- invalid_iterator.202 is thrown by overloads 1 and 2 only; the page said
  "every overload" and then excluded 3 in parentheses.
- The note on links says what moves (the links after the removed element,
  as insert() moves them the other way) and that every iterator over the
  container is invalidated, not only those after the removed element.
- edit.hpp compares keys with document_data::key_equals().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of editable json documents

From gregmarr's review of #5630:

- set() with a JSON pointer below a null value checks for "-" before
  scanning the token for digits.
- node_flags::storage is defined as escaped | edited, and the comment on
  document_data::base says that the fourth slot is unused.
- source_extent() names its root check.
- The integer-index overloads of set(), insert(), and erase() use a named
  trait instead of a negated condition, which astyle formats like the
  library's other enable_ifs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the rest of the review of editable json documents

From gregmarr's review of #5630:

- alloc_nodes() gives a request of half of the next chunk or more a chunk
  of its own, so that the rest of the current chunk stays available for
  smaller requests instead of being abandoned.
- The comments on edit_state, append_text(), and reserve_moved() say that
  views stay valid across every edit (not just the first), that n is 0 for
  empty strings, and why both capacities are checked.
- The architecture page names flag bits 3, 4, and 5 (linked) separately
  and says that a discarded view has no node; erase.md rewords overload 3
  and says why it never throws invalid_iterator.202.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Call alloc_nodes() before reading chunks.back() in the json_view chunk test

The operands of == are unsequenced, and MSVC evaluated
d->edits->chunks.back() before alloc_nodes() added the new chunk, so the
check compared the result with the previous chunk and failed on
msvc-arm64 (Debug and Release).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:29 +02:00
Niels Lohmann 3ff37ead75 Scan json_view strings with SIMD and index large objects (#5628)
* Scan json_view strings with SIMD and index large objects

Speed up json_view's parser with SIMD scanning and a hash table
for large objects.

Long runs of string bytes are scanned 16 bytes at a time with NEON
(AArch64, GCC and Clang) and SSE2 (x86-64), both baseline
instruction sets. Keys keep 16 table checks before the vector
loop, because their lengths repeat from record to record; string
values get 8, because their lengths vary more. Non-ASCII text is
validated 16 bytes at a time with simdjson's "lookup4" check
(Keiser and Lemire, 2021), with NEON on AArch64 and, on x86-64,
with SSSE3. SSSE3 is not part of baseline x86-64, so the check is
compiled for SSSE3 with a function attribute and used only where
CPUID reports it, which all x86-64 CPUs since about 2011 do; the
answer is cached in a statically initialized atomic, so there is
no guard of a local static and no global constructor. The same
input is accepted either way. JSON_VIEW_NO_SIMD selects the
portable code.

On x86-64, string runs are now checked vector-first: one SSE2
compare from the first byte finds the end of most keys and short
values, instead of a branch per byte for the first 8-16 bytes.
AArch64 keeps the byte-wise steps, where a NEON mask costs more and
the branches predict well. Entering an object or array no longer
stalls: open() stores the parent's frame field by field instead of
building it on the stack and reading it back with wider loads,
which waited for the narrower stores to retire.

Objects with 128 members or more get an open-addressing hash table
built when the object closes, so operator[], at(), find(),
contains(), count(), value(), and JSON pointers take constant time
on average in such objects; of duplicate keys, the first is kept,
as for the linear search. The idea comes from Boost.JSON.

simdjson is credited in simd.hpp's SPDX block, the README, and
license.md.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix GCC module build and MSVC C4127 in the json_view SIMD code

GCC ignores the target attribute in modules, so the SSSE3 dispatch is
disabled for the module interface (the check stays portable, SSE2 is kept).
Make the 8-vs-16 byte unrolling condition in scan_string_run a
preprocessor/template split to avoid a constant condition (C4127).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove an accidentally committed GCC module cache

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Bound the probe length of the large-object hash index

The key hash is not seeded, so keys chosen to collide made building the table quadratic (20,000 colliding keys took 470 ms to parse). A key may now sit at most 64 slots from its home slot; if a key would sit further away, the table is dropped and the object is searched linearly. Lookups stop after the same distance.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Release the spare capacity of the hash index in shrink_to_fit

shrink_to_fit() now trims the tables of large objects like the node array and the decoded strings, and the list of large objects is released as soon as the tables are built.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use the portable string scan on non-x86 targets that define __SSE2__

WebAssembly with -msse2 defines __SSE2__, but has no <cpuid.h> or x86 intrinsics headers.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document the hash index number in node::extra

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep the last of duplicate keys in the object hash index

Lookups in objects with 128 members or more now return the last member of
a repeated key, like the linear search of smaller objects and like
materialize() and basic_json::parse(). build_object_index let the first
occurrence win, so the same text gave different results depending on the
size of the object.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep the first of duplicate keys in the object hash index again

Lookups in objects with 128 members or more return the first member of a
repeated key again, like the linear search of smaller objects. This undoes
the code change of a69542046; its test now expects the first member from
lookups (with and without a table) and the last value from materialize()
and basic_json::parse().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy 22 findings in the json_view headers

Silence bugprone-casting-through-void for the SSE loads (a reinterpret_cast
would trip -Wcast-align=strict), use auto for a cast initialiser, add
parentheses to a mixed expression, and name bugprone-std-namespace-modification
in the NOLINTs of the tuple_size/tuple_element specialisations.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy 22 findings in unit-json_view.cpp and unit-json_view_builder.cpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid GCC useless-cast and strict-overflow warnings in unit-json_view.cpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Index strings with size_t in the colliding-keys test (MSVC C4244 on Win32)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Move the dump, comparison, and large-object tests to unit-json_view_dump.cpp

The dump and comparison tests moved to their own file in the dump pull
request; the hash index tests of this branch join them there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Follow the renames from the review of the view builder

The review of #5620 renamed document_data's tape/tape_size/tape_cap to
nodes/node_count/node_capacity and made the decode arena a
std::vector<char>; adapt the code of this change to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Rename slots in the object index for Qt compatibility

Qt defines slots as a macro, so a Qt header included before
json_view.hpp broke the declarations of the local variable (see #5865).
The test variable is renamed as well.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of the json_view object index and SIMD scan

From gregmarr's review of #5628:

- Docs and comments say that the hash index is one of the object's keys.
- object_index.hpp: conditional_static_cast for the hash, "slot" for the
  table position, no flag for a degenerate table, and a static_assert
  that the smallest indexed object is larger than the probe window.
- scan.hpp unrolls with NLOHMANN_VIEW_REPEAT_FIRST_8/SECOND_8.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Encode JSON_VIEW_NO_SIMD and JSON_VIEW_USE_SSSE3 in the ABI namespace

Both macros select between definitions of the same inline functions, so
translation units that disagree on them violated the ODR. Like the other
ABI-affecting macros, they now add a tag to the inline namespace (_vns and
_vssse3), and the docs replace the "Define consistently" warning with the
usual ABI compatibility note. From gregmarr's review of #5628.

nlohmann_json.natvis covers twice as many tags now, so it lists four times
as many namespaces (14.7 MB instead of 3.4 MB).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Do not include type_traits.hpp directly in the object index

json.hpp, which object_index.hpp already includes, provides it; included
directly, the json_view amalgamation (which only treats nlohmann/json.hpp
as external) copied the json internals into json_view.hpp a second time,
so the fuzzers, which include both single headers, failed with
redefinitions (make_void, integer_sequence, priority_tag, ...).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Compare keys with key_equals() in the object hash index

As the linear lookups do since the second review round of #5622.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:28 +02:00
Niels Lohmann 4c3a3eb5b5 Add dump() and comparisons to json_view (#5624)
* Add dump() and comparisons to json_view

Add basic_json_view::dump() and the comparison operators, and read
floats from the parser's digit layout instead of rescanning the
token.

dump(indent, indent_char, ensure_ascii, number_format) writes a
value the way ordered_json::parse(text).dump() writes it for the
same arguments: members in document order, all of them should a
key occur more than once; strings escaped by the same rules, using
the library's scanning kernels; floats written with the library's
to_chars conversion, so the output equals basic_json's byte for
byte; integers copied from the source, where they are already
canonical, except -0, which parse() reads as 0. There is no
error_handler argument, because the view only holds valid UTF-8.
number_format::source copies numbers exactly as they appear in the
source (e.g. "1.50", "1E2", "-0"), which basic_json cannot provide.
operator<< takes the indentation from the stream width, as for
basic_json. The writer walks iteratively, so nesting depth is
limited by memory only.

operator== and operator!= compare two views, or a view and a
basic_json value in either order, by the rules basic_json's
operator== uses: numbers compare by value across their types,
objects compare by their members with duplicate keys resolved as
parse() resolves them, member order matters only where the object
type keeps one, and discarded views compare as discarded basic_json
values do, including under JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON.
Nothing is materialized except single scalars.

While parsing, the view now records where the integer digits, the
fraction digits, and the exponent of a float token are, so floats
and doubles with at most 19 digits are read from that layout with
the library's decimal_to_float() instead of rescanning the token.
Both round correctly, so the values are those of parse(). get<double>(),
materialize(), dump(), and the comparisons all use it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid raw string with escapes inside CHECK macro (MSVC C2017)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Size the dump buffer of a small value by its own extent

The source extent of a value was read from the next node, falling back to the rest of the document when that node held a decoded string. dump() of a small value could thus allocate a buffer as large as the document. Skip a few such nodes, cap the fallback estimate, and shrink a buffer that is much larger than its output.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Hold the documents of the dump and comparison tests in names

root() of a temporary document is deleted: its views would dangle.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Do not shrink the output of dump()

finish() copied the whole output when the buffer was more than twice as
large as the result (citm dump +11%). The tighter source_extent()
estimate already keeps the buffer of a small value small.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Document and test first-wins lookups next to dump() and ==

dump() writes every member and == resolves duplicate keys as parse()
does, whereas lookups find the first member of a duplicate key.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the clang-tidy 22 findings in json_view and its tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Split the dump and comparison tests off unit-json_view.cpp

The MinGW linker of the Windows clang jobs cannot link object files with more
than 32767 sections ("relocation truncated to fit: IMAGE_REL_AMD64_REL32
against `.rdata'"). unit-json_view.cpp reaches that limit as the stack
grows, so its "json_view dump" and "json_view comparison" test cases move
into unit-json_view_dump.cpp. The test generator and has_duplicate_keys()
that both files use move into json_view_test_helpers.hpp.

The new file mentions JSON_HAS_CPP_17, so it is built for C++17 like the file
it was split from.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Follow the renames from the review of the view builder

The review of #5620 renamed document_data's tape/tape_size/tape_cap to
nodes/node_count/node_capacity and made the decode arena a
std::vector<char>; adapt the code of this change to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep the stream includes of json_view.hpp for IWYU

json_view.hpp uses std::istream and std::ostream itself, but IWYU sees
them through the exported json.hpp and asked to remove both includes,
which fails ci_single_binaries (single_json_view).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of json_view dump() and comparisons

From gregmarr's review of #5624:

- The output buffer takes string literals without a length.
- write_float() names the IEEE single/double condition once, as the
  library's serializer does.
- equal() checks the types first and compares with !=; the reference to
  the top frame is noted as dangling after pop_back().
- dump.md: one paragraph per case in the notes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Exclude all json_view test files from Infer's reports

The same std::unique_ptr false positive (USE_AFTER_DELETE) also shows for
two json_documents parsed in one lambda in unit-json_view_dump.cpp
(ci_infer). It affects any test that holds several documents, so the
exclusion now covers tests/src/unit-json_view*.cpp; ASan and Valgrind
cover these tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Allow the string literal parameter of the view serializer's put() for clang-tidy

The overload takes the literal by array reference so that its length is a
template parameter, like scan_literal() and the C array overloads of
to_json() and from_json().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Bind the serializer's frame only after the check for its end

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:28 +02:00
Niels Lohmann 43e97b629b Add element access, iteration, values, and JSON pointers to json_view (#5622)
* Add element access, iteration, values, and JSON pointers to json_view

Give basic_json_view the read-only access functions of basic_json:
operator[] and at() with keys and indices, front()/back(), find(),
contains(), count(), begin()/end() and cbegin()/cend(), items()
with structured bindings from C++17 on, and type_name().

Exceptions have the ids and messages of the const functions of
basic_json. Where basic_json has undefined behavior the view
answers safely: operator[] with a missing key or an out-of-range
index returns a discarded view, and front()/back() of an empty
container throw invalid_iterator.214. Objects are iterated in
document order, and all members are visited; duplicate-key lookups
find the first member (as yyjson and simdjson do), while parse(),
materialize(), and the map conversions keep the last value, as
parse() does. Keys of up to 16 bytes are compared with two
overlapping loads.

Add value conversions: get<T>()/get_to() for arithmetic types,
bool, nullptr_t, strings (std::basic_string copied,
string_view_t without a copy), BasicJsonType, views, std::vector,
and maps with string keys; get_string() for the string without a
copy; number_token() for the number exactly as written in the
source; value() with keys and JSON pointers; and operator[]/at()/
contains() with JSON pointers. Everything else, including types
with from_json(), goes through materialize() of that subtree.
get<T>() of arithmetic types is inlined down to the conversion, so
reading an integer needs no call.

detail::json_pointer_access exposes a pointer's reference tokens
to code outside basic_json.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make view lookups last-wins and chained access safe

* Lookups (operator[], at, find, contains, count, value, JSON pointers)
  return the last member of a duplicate key, as materialize() and
  parse() keep it.
* operator[] on a discarded view returns a discarded view instead of
  throwing, so v["a"]["b"] is safe for a missing "a".
* operator[] and at() take any integer type (not only int and size_t),
  fixing ambiguous calls with unsigned, long, std::int64_t, ...
* Fix the operator[] documentation, which claimed a discarded view for
  a type mismatch where type_error.305 is thrown.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Replace the removed operator bool of basic_json_view in the header and its tests

A view is tested with is_discarded(); the lookups in at(), value() and
contains(json_pointer) and the unit tests no longer rely on the explicit
conversion to bool.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Use named documents in the json_view tests that called root() on a temporary

root() of an rvalue document is deleted, because the views would dangle.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test discarded views with is_discarded() in the operator[] docs and examples

The explicit conversion to bool of basic_json_view is gone.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Return the first member of duplicate keys from lookups again

Looking up the last member of a duplicate key cannot stop at a match, so
every lookup scanned the whole object (1.6 to 3.4 times slower for small
objects). operator[](key), at, find, value, contains, count, and JSON
pointer resolution return the first member again, as yyjson and simdjson
do; materialize() and get<map>() keep the last value, as parse().

The documentation says so in the feature page and on each lookup page,
and explains how to get the value parse() would give. The integer index
templates and the discarded chaining of operator[] stay.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Do not force-inline the view's functions on MSVC

__forceinline makes MSVC report C4714 for function templates it cannot
inline (get<std::string>), an error under /WX. The forced inlining is
only a performance hint, so use plain inline there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

Mark the includes that are needed by design (json.hpp, the macro
unscope, <tuple> for the tuple_size specialization), take size_t from
<cstring>, and drop the forward declaration of basic_json_document that
the friend declaration makes redundant.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid GCC useless-cast warnings in the integer index tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix the clang-tidy 22 findings in json_view and its tests

The same changes as on the dump branch, where they were first made, so
that this branch passes clang-tidy on its own.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Report one-character non-numeric array indices like basic_json in the view

Develop's #5800 made basic_json report a JSON pointer array index such as
"/a" as parse_error.109 instead of out_of_range.404, as longer ones were;
the view's array_index() follows, with "-" and the empty token still
unresolved.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of json_view element access

From gregmarr's review of #5622:

- count() counts every member with the key, so count(key) > 1 detects
  duplicate keys; it was contains() as 0/1 before.
- get_to() passes types that get<T>() does not convert directly to
  from_json with the existing value, as basic_json::get_to() does, so that
  a from_json which sets only the fields present keeps the others.
- array_index() checks the empty token and "-" first; the results are
  unchanged.
- Docs: no "if an exception is thrown" where a view never changes
  anything; the borrowed source must also stay unmodified; items() on a
  temporary view is safe; back()/front() notes shortened; the basic_json
  see-also entries say how the view's results can differ.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Count matching keys as unsigned in the json_view access test

The conditional added 1 or 0 as int to a std::size_t, which GCC rejects
with -Werror=sign-conversion (ci_test_standards_gcc).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the second review round of json_view element access

From gregmarr's review of #5622:

- begin()/end(): "a null value or a discarded view iterates as an empty
  range", singular like the primitive case before it.
- The class page's list of function groups has no trailing commas.
- document_data::key_equals() compares a key node with a string, for the
  key comparisons that were spelled out in each lookup.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Write the negated enable_if conditions of json_view with negation<>

With a ! or && in the condition, astyle spaces the whole template
header (template < ... >); detail::negation and a named trait keep it
compact, as the library writes such conditions elsewhere. From gregmarr's
review of #5630.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep detail::negation out of json_view.hpp's enable_ifs

include-what-you-use asks json_view.hpp to include
detail/meta/type_traits.hpp for it, which the single header must not do
(the amalgamation would duplicate json's internals). The two conditions of
value() go through a trait in value.hpp, like the others.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:27 +02:00
Niels Lohmann 122d718330 Add json_document and json_view: node index, parser, and document (#5620)
* Add json_document and json_view: node index, parser, and document

Add json_document and json_view, a read-only, zero-copy index of a
JSON text, as the first public slice of the zero-copy view (#5295).

A parse produces a flat array of 16-byte nodes in document order,
one per value and one per object key. Strings stay in the source
text; escaped strings are decoded into an arena. Integers are
converted while their digits are in the cache; floats keep only
their digit layout and are converted on read. Containers store the
size of their subtree, so a reader can step over one in constant
time. A document makes a handful of allocations, however many
values it has.

The parser accepts exactly what json::parse accepts, with every
combination of ignore_comments and ignore_trailing_commas, with and
without a trailing NUL, and under JSON_STRICT_NUL_HANDLING. It is
portable C++11 and does not depend on byte order.

basic_json_document adds parse, parse_copy, accept, read (reuses a
document's memory), root, is_discarded, source, owns_source,
node_count, memory_usage, and shrink_to_fit. basic_json_view adds
type, the is_* queries, operator bool, size, empty, materialize,
and source_offset. A parse error throws the same exception
basic_json::parse would throw for the same input, message and
position included.

detail::abi_config keeps JSON_STRICT_NUL_HANDLING and
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON readable after json.hpp
undefines them, in the ABI namespace so they always match the
basic_json in use.

A NUL byte that ends a // comment is the end of the input, as in
parse() since #5696.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix document_data for old Clang (3.4-3.6)

Drop the empty braced NSDMIs of the std::string members: old Clang
rejects the defaulted constructor when it is used by a member
initializer before the end of the class definition.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Exclude libstdc++'s string_view comparison from the sign-change check

basic_string_view::_S_compare stores the difference of two lengths in a
signed difference_type on purpose, which -fsanitize=integer reports for
every comparison with a shorter view (test-json_view_cpp17).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Reject integer lengths in json_document::parse

parse(ptr, len) compiled: len converted to allow_exceptions, and ptr was
read as a C string, past the end of a buffer without a terminating NUL.
Delete the overloads of parse, parse_copy, accept, and read that take an
integer other than bool where the flags are expected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Copy const rvalue strings and document the accepted inputs

json_document::parse(std::move(const_string)) failed to compile with
"no matching read_kind": only a non-const rvalue std::string can be moved
from. Treat a const rvalue as a copied byte container.

parse() does not accept everything BasicJsonType::parse() does: a FILE*
and pointers to or arrays of wide characters are rejected at compile time.
List the supported inputs instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove the conversion to bool from json_view

explicit operator bool meant "refers to a value", which silently differs
from what a basic_json converts to. Use !v.is_discarded() instead.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Delete json_document::root() on temporary documents

auto v = json_document::parse(text).root() compiled and left the view
dangling. Delete the overload for rvalue documents, take a named document
in the tests, and document the lifetime rule.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Store node integers by halves on big-endian targets

The non-little-endian path of the node writer wrote the integer's native
word over len and next, so len got the high half there. Compose and split
the value explicitly (len is the low half, next the high half); the
little-endian path stays a plain memcpy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Check the node array size for overflow

reserve(n) and the growth of the index computed n * sizeof(node) without a
check, which wraps around on 32-bit targets for inputs of about 1 GiB and
allocates a too small array. Throw std::bad_alloc for a count beyond the
address space and clamp the growth step to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* State the exact input size limit of json_document

The check rejects inputs of 0xFFFFFFF0 bytes or more, but the exception
message and the documentation said 4 GiB. Name the limit once
(max_input_size), and state 4 GiB minus 16 bytes in the message and the
documentation. Test the limit with a container that only claims the size.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove the unused legacy_discarded_value_comparison from abi_config

Nothing reads it: the view does not compare values.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Align the banner comment of json_view.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fuzz json_document with exact-size buffers and parse options

The fuzzer only parsed a std::string with default options. Also parse an
exact-size byte vector, which has no NUL after its last byte and takes the
bounds-checked path, and derive ignore_comments and ignore_trailing_commas
from the first input byte, comparing against json::parse and json::accept
with the same options.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Give document_data a user-provided constructor

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in unit-json_view_builder.cpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Annotate intentional patterns in unit-json_view.cpp for clang-tidy

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Suppress Flawfinder's read() finding on the deleted overload

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Check the steps of the deep-nesting test separately

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make max_input_size a function (GCC -Wunused-const-variable)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make json_view.hpp pass include-what-you-use

ci_single_binaries runs IWYU with --error on every header. For json_view.hpp
it suggested adding <array> (std::array is used), the headers that json.hpp
already provides (abi_config, abi_macros, input_adapters, json_pointer,
cpp_future, string_concat, value_t, json_fwd), and <version> for
std::nullptr_t, and removing <cstddef>.

- include <array>
- keep <cstddef> (nullptr_t, size_t; IWYU attributes them to <version> and <cstring>)
- tell IWYU not to suggest the headers that json.hpp provides: the amalgamated
  json_view.hpp only includes json.hpp, so including them here would duplicate
  their definitions
- export json.hpp, keep macro_unscope.hpp, and drop the unused forward declaration of the document class

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Read the node array base after emit() in the view builder's open()

emit() moves the node array when it grows, and the subtraction read base
in the same expression, so the order was unspecified. MSVC Release builds
without forced inlining read the old base; the container index then
pointed outside the array and close() wrote out of bounds.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Silence a clang-tidy use-after-move finding in the json_view test

const_text is const, so std::move does not move from it; the second call
is part of the const rvalue test.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate the amalgamated headers

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review of the view builder and document

Changes promised in the replies to the review of #5620:

- read() reuses the owned copy: a copied input (an rvalue byte container,
  a stream, FILE*, wide characters, an iterator range) is appended to the
  cleared owned buffer instead of a fresh std::string being move-assigned
  over it. A moved-in std::string still replaces it.
- The error replay runs the library parser with a SAX handler that stores
  nothing and throws the reported exception, instead of
  BasicJsonType::parse(), which built a DOM up to the error.
- Pointers to and arrays of wide characters, and FILE*, take the input
  adapter path (converted to UTF-8 into an owned buffer) instead of
  failing a static_assert. Wide pointers are NUL-terminated.
- Streams are read in blocks through rdbuf() into the buffer, instead of
  character by character through the input adapter; eofbit is set at the
  end, as by the library's stream adapter.
- tape/tape_size/tape_cap are now nodes/node_count/node_capacity.
- The builder's members get descriptive, m_-prefixed names.
- The shallow frame stack is a std::array (in a wrapper with a
  user-provided constructor: -Weffc++ wants the member initialized, and
  value-initializing the 64 frames costs up to 30 % on small documents).
- The value dispatch macro has no NEXT parameter; the switch is left with
  break, and the macro is undefined after its last use.
- literal() and digits_exceed() deduce the length from the string literal;
  each literal has one helper; the two `if (!is_float)` blocks are merged.
- arena_ensure() sits right before the writes; the decode arena is a
  std::vector<char>.
- The builder records the deepest nesting, and materialize() sizes its
  stack from it once (growing it only if a subtree is deeper).
- Comment fixes (hex4, slow_string, the NUL rule, put_utf8, 0 - m, the
  arena resize, grow()) and formatting (macro backslashes, digit cases,
  one power of ten per line, value_t assertion next to is_container()).
- build_with's template parameters follow the builder's order.

Greg's idea of keeping the source offset in `off` and the arena offset in
`next` for escaped strings was measured and not adopted: string access got
7-18 % slower with a select, and 3.5-18 % slower with a branch-free field
pick (Apple M-series, clang -O3), parsing was unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Apply the review's wording fixes to the json_view documentation

- Copying is disabled because of its cost, not because of two documents
  borrowing the same buffer; the unique_ptr follows from move-only.
- Inputs: streams, FILE*, and wide characters (strings, arrays, pointers)
  are all supported and owned; point to parse_copy() for owned copies.
- The error replay only runs for failing input, builds no values, and is
  skipped with allow_exceptions = false.
- read() writes copied inputs into the previous owned copy.
- Views can dangle like std::string_view, and why there is no
  shared/weak pointer design.
- The 4 GiB limit (16-byte nodes, 16-byte scanner steps), streams being
  read to the end first, which macros apply, why materialize() has no
  source positions, and wording nits (accept() vs basic_json::accept(),
  is_binary, is_discarded, see-also lists, size table, "back into").

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Say that json_view converts integers while parsing

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Address the review: null inline nodes, trait spelling, and max() in json_view

- inline_nodes is null when the document has no inline nodes, instead of
  pointing past the end of the allocation; shrink_to_fit() no longer
  passes it to memcpy() for an empty node array
- is_byte_char and is_wide_char use conjunction/disjunction, which
  astyle leaves unspaced
- append_stream() uses (std::max) for the count read

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Include materialize.hpp in the view builder test

The test calls detail::view::materialize(), which only the single header
pulled in; the multiple-header build (JSON_TEST_USING_MULTIPLE_HEADERS)
failed with "'materialize' is not a member of detail::view".

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Rename emit() in the view builder for Qt compatibility

Qt defines emit as an empty macro, so a Qt header included before
json_view.hpp broke the declaration of the builder's emit(), see
issue #5865. It is now called push_node().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Do not copy an empty run into an unallocated arena

memcpy() must not be passed a null pointer even for zero bytes, which
arena_run() did for an empty run while the arena was still empty
(ci_test_clang_sanitizer: "null pointer passed as argument 1" in
builder.hpp). Only the slow path can see such a run.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Exclude the view builder test from Infer's reports

Infer's std::unique_ptr model confuses the values that the test's build()
helper returns by value and reports USE_AFTER_DELETE for one of them after
the destructor of an unrelated temporary (ci_infer, 12 reports, all in
tests/src/unit-json_view_builder.cpp). AddressSanitizer is clean on the
same tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the deleted-overload checks of json_view on MSVC 2015

MSVC 2015 does not treat selecting a deleted function in decltype as a
substitution failure, so is_detected reports the deleted
parse/parse_copy/accept/read(ptr, len) overloads as callable and the
static_asserts in "integer arguments do not compile" fail (AppVeyor
VS2015 jobs). Calling them still fails to compile there. Guard the
negative checks as unit-delete_deprecated_functions.cpp does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 10:21:27 +02:00
Niels Lohmann a4974e72c1 Run the pull request's natvis generator in the amalgamation check (#5873)
The check ran generate_natvis.py from the develop checkout and took only
the template from the pull request, so a pull request that changes the
generator (such as #5868, which limits the namespaces to two ABI tags)
could never pass: develop's generator rewrote the committed file. The
check now runs the pull request's generator, which loads its own
template, as the macro_builder step already does.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-21 09:55:40 +02:00
Niels Lohmann a55cae45fd Test that the contiguous fast paths never read outside the input (#5826)
* Test that the contiguous fast paths never read outside the input

The bulk string scanner, the SWAR searches, the UTF-8 prefix validator,
the 8-digit number loop, the 8-byte indentation skip, and the BSON/BON8
bulk string readers load several bytes at a time from contiguous input.
ASan misses over-reads into the slack of the same heap allocation, and
fuzzers rarely end an input exactly at a page boundary.

The new test copies each input directly in front of, and directly
behind, an inaccessible page (mmap/mprotect on POSIX,
VirtualAlloc/VirtualProtect on Windows), so reading even one byte
outside the input crashes. It covers the string-scan functions directly
and json::parse, accept, sax_parse (with and without comments) and all
binary readers through the public API, with inputs ending at every byte
of strings, escapes, UTF-8 sequences, numbers, literals, indentation
runs, and binary documents. Each result must match parsing the same
input from a std::string.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Rename the reader parameter to avoid a flawfinder false positive

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Make the guard page test work without exceptions and under UBSan

Under JSON_NOEXCEPTION, the error paths abort instead of throwing, so the
outcomes are now computed with allow_exceptions=false there. Copying the
input with std::copy from char to unsigned char tripped Clang's implicit
conversion sanitizer; std::memcpy avoids the conversion.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Test parse_float_native() at the guard page

parse_float_eisel_lemire() was removed by #5738, so the test no longer
compiled. The lexer converts floats from its token buffer, so the JSON
section never places a float token at the page boundary; the section now
calls parse_float_native() for float and double on every complete number
token directly and compares the result bit for bit with converting the
same token from a std::string.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix clang-tidy findings in the guard-page tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Convert explicitly between bytes and characters in the guard-page tests

The clang sanitizer job (-fsanitize=integer, x86-64 where char is signed)
reported the implicit char -> unsigned char conversions of std::copy() into
the guarded buffer and of the std::string built from the binary inputs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-20 22:24:09 +02:00
dependabot[bot] 667e296707 Bump ossf/scorecard-action from 2.4.4 to 2.5.0 (#5870)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.4 to 2.5.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/2d1146689b8cda280b9bc96326124645441f03bc...fa7117db83c52d787ae5a89f350999701505d78e)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-20 22:23:22 +02:00
dependabot[bot] ec2e50e26f Bump cppcheck from 1.5.2 to 1.5.3 in /cmake/requirements (#5850)
Bumps [cppcheck](https://github.com/msclock/cppcheck-wheel) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/msclock/cppcheck-wheel/releases)
- [Commits](https://github.com/msclock/cppcheck-wheel/compare/v1.5.2...v1.5.3)

---
updated-dependencies:
- dependency-name: cppcheck
  dependency-version: 1.5.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-20 22:22:10 +02:00
Niels Lohmann 4da932f518 Make parsing from std::istream and FILE* fast (#5828)
* Make parsing from std::istream and FILE* fast

Squashed onto develop cd6b428ef (CI fixes from #5860). Original commits:

- Let the lexer's bulk fast paths run on a window of the input
- Scan the get area of an input stream in bulk
- Lock a FILE once per parse instead of once per character
- Add benchmarks for parsing from a FILE* and for many values from a stream
- Avoid implicit unsigned char to char conversion in stream tests
- Make file_input_adapter::unlock() safe to call on a moved-from adapter

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Add ABI tags for JSON_DISABLE_STREAM_BULK_SCAN and JSON_DISABLE_UNLOCKED_FILE_IO

Both macros select between two definitions of an input adapter, so
translation units compiled with different settings violated the ODR when
linked together. Like the other ABI-affecting macros, they now append a
tag to the inline namespace: _dsbs and _dufio. Their defaults move to
abi_macros.hpp so that json_fwd.hpp sees them, and the natvis file is
regenerated for the new namespaces.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Skip the exception-only checks of the stream input tests without exceptions

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-19 22:00:58 +02:00
Niels Lohmann a2248ed5d5 Test host-toolchain CI jobs on ubuntu-26.04 (#5867)
* Run host-toolchain CI jobs on ubuntu-26.04

GitHub moves ubuntu-latest to Ubuntu 26.04 between 2026-10-19 and
2026-11-19 (actions/runner-images#14748). Run the jobs that use the
runner's own compilers and tools on ubuntu-26.04 now to find breakage
before the rollout. Container jobs are unaffected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Let lcov continue on inconsistent coverage data in the coverage job

On ubuntu-26.04, lcov reports a mismatched end line for a function of the
test helper value_in_range_of_test.hpp in the 32-bit build and stops
(ci_test_coverage). The report only covers the library sources, so ignore
the inconsistency like the mismatch and unused errors before.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-19 22:00:43 +02:00
Niels Lohmann cc8334113c Do not use Qt's keyword macros as identifiers (#5866)
* Do not use Qt's keyword macros as identifiers

The prefix tree used by unflatten() (#5836) had a member named slots, which
Qt defines as an empty macro, so including a Qt header before this library
failed to compile. Rename it to table and add a test that includes the
library with Qt's keyword macros (slots, signals, emit, foreach, forever)
defined.

Fixes #5865

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Keep the Qt keyword macros in the test from failing -Wunused-macros

Clang (-Werror) rejected the macros that the test defines but the library
must not use, and clang-tidy asked for parentheses around the arguments
of the Q_FOREACH imitation, where the variable is a declaration.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-19 22:00:31 +02:00
118 changed files with 575865 additions and 1414 deletions

No files matched your search

+15 -16
View File
@@ -37,7 +37,7 @@ jobs:
path: pr/ path: pr/
check: check:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
env: env:
MAIN_DIR: ${{ github.workspace }}/main MAIN_DIR: ${{ github.workspace }}/main
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
@@ -77,13 +77,12 @@ jobs:
cd $MAIN_DIR cd $MAIN_DIR
# Built from this PR's own tools/macro_builder/main.cpp, not a # Built from this PR's own tools/macro_builder/main.cpp, not a
# develop checkout: unlike amalgamate.py and generate_natvis.py, # develop checkout: unlike amalgamate.py, this tool has no other
# this tool has no other source of truth to check against (its # source of truth to check against (its own README documents that
# own README documents that it must reproduce macro_scope.hpp # it must reproduce macro_scope.hpp byte for byte), so there is
# byte for byte), so there is nothing to gain from checking out a # nothing to gain from checking out a separate copy, and doing so
# separate copy, and doing so would make this step fail on a PR # would make this step fail on a PR that adds support for a new
# that adds support for a new dispatch table until that PR itself # dispatch table until that PR itself merges to develop.
# merges to develop.
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX) TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder" c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp" "$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
@@ -146,14 +145,14 @@ jobs:
- name: Regenerate nlohmann_json.natvis - name: Regenerate nlohmann_json.natvis
run: | run: |
cd $MAIN_DIR cd $MAIN_DIR
# The template is what this check verifies the committed natvis # The generator and its template are what this check verifies the
# file against, so take it from this PR rather than from the # committed natvis file against (which namespaces it lists, and
# develop checkout: generate_natvis.py loads the template from its # what each entry looks like), so run this PR's copy rather than
# own directory, and with develop's copy a PR that changes the # the develop checkout's: with develop's, a PR that changes either
# template could never pass. Like the macro_builder step above, # could never pass. generate_natvis.py loads the template from its
# this only renders PR content in a job without credentials. # own directory. Like the macro_builder step above, this only runs
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/ # PR content in a job without credentials.
python3 $NATVIS_TOOL_DIR/generate_natvis.py $MAIN_DIR python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
- name: Build patch and check for differences - name: Build patch and check for differences
id: diff id: diff
+1 -1
View File
@@ -21,7 +21,7 @@ permissions:
jobs: jobs:
CodeQL-Build: CodeQL-Build:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
permissions: permissions:
security-events: write security-events: write
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
name: Scan name: Scan
runs-on: ubuntu-latest runs-on: ubuntu-26.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
+6 -6
View File
@@ -32,7 +32,7 @@ jobs:
run: cmake --build build --target ci_test_gcc run: cmake --build build --target ci_test_gcc
ci_meson_install: ci_meson_install:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
steps: steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with: with:
@@ -77,7 +77,7 @@ jobs:
cmake --build build-import-split cmake --build build-import-split
ci_infer: ci_infer:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
@@ -106,7 +106,7 @@ jobs:
if-no-files-found: ignore if-no-files-found: ignore
ci_static_analysis_ubuntu: ci_static_analysis_ubuntu:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
strategy: strategy:
matrix: matrix:
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
@@ -175,7 +175,7 @@ jobs:
run: cmake --build build --target ${{ matrix.target }} run: cmake --build build --target ${{ matrix.target }}
ci_test_coverage: ci_test_coverage:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
@@ -430,7 +430,7 @@ jobs:
run: cmake --build build --target ci_nvhpc run: cmake --build build --target ci_nvhpc
ci_emscripten: ci_emscripten:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
@@ -450,7 +450,7 @@ jobs:
run: cmake --build build run: cmake --build build
ci_test_documentation: ci_test_documentation:
runs-on: ubuntu-latest runs-on: ubuntu-26.04
strategy: strategy:
matrix: matrix:
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid] target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
+4
View File
@@ -1,6 +1,7 @@
{ {
"_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.", "_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.",
"_comment_pulse": "Pulse stops exploring paths after pulse-max-disjuncts (default 20). With the default, basic_json::replace_value() (destroy + assert_invariant) exceeds the limit, Pulse loses the stored type, and reports false NULLPTR_DEREFERENCE findings for get_ptr() results in tests/src/unit-pointer_access.cpp.", "_comment_pulse": "Pulse stops exploring paths after pulse-max-disjuncts (default 20). With the default, basic_json::replace_value() (destroy + assert_invariant) exceeds the limit, Pulse loses the stored type, and reports false NULLPTR_DEREFERENCE findings for get_ptr() results in tests/src/unit-pointer_access.cpp.",
"_comment_view": "Infer's model of std::unique_ptr mixes up values that are returned by value and own one: basic_json_document (parse() results) and the `built` values of the build() helper in tests/src/unit-json_view_builder.cpp. It reports USE_AFTER_DELETE for one of them after the destructor of an unrelated object (e.g. ~built() on line 345 invalidating `shallow` on line 350 of that file, or two documents parsed in one lambda in tests/src/unit-json_view_dump.cpp). AddressSanitizer and Valgrind are clean on the same tests, so reports for the json_view test files are excluded; findings reported in the library headers are still reported.",
"pulse-max-disjuncts": 40, "pulse-max-disjuncts": 40,
"fail-on-issue": true, "fail-on-issue": true,
"disable-issue-type": [ "disable-issue-type": [
@@ -10,5 +11,8 @@
"PULSE_UNNECESSARY_COPY_OPTIONAL", "PULSE_UNNECESSARY_COPY_OPTIONAL",
"PULSE_RESOURCE_LEAK", "PULSE_RESOURCE_LEAK",
"PULSE_CONST_REFABLE" "PULSE_CONST_REFABLE"
],
"report-block-list-path-regex": [
"tests/src/unit-json_view.*\\.cpp"
] ]
} }
+3 -3
View File
@@ -361,10 +361,10 @@ add_custom_target(ci_test_coverage
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32 COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
COMMENT "Compile and test with coverage" COMMENT "Compile and test with coverage"
) )
+1 -1
View File
@@ -1 +1 @@
cppcheck==1.5.2 cppcheck==1.5.3
@@ -132,6 +132,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
- [operator>>](../operator_gtgt.md) - deserialize from stream - [operator>>](../operator_gtgt.md) - deserialize from stream
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input - [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
instead of treating it as end of input instead of treating it as end of input
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
at a time
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
functions
## Version history ## Version history
@@ -143,6 +147,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating - `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
it as end of input; planned to become the default in version 4.0.0. it as end of input; planned to become the default in version 4.0.0.
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
!!! warning "Deprecation" !!! warning "Deprecation"
+2 -1
View File
@@ -93,7 +93,8 @@ Logarithmic in the size of the JSON object.
- [count](count.md) returns the number of occurrences of a key - [count](count.md) returns the number of occurrences of a key
- [contains](contains.md) checks whether a key exists - [contains](contains.md) checks whether a key exists
- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view - [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view (a duplicate key resolves
to the first member, whereas `parse()` keeps the last)
## Version history ## Version history
+2 -1
View File
@@ -51,7 +51,8 @@ Constant.
## See also ## See also
- [back](back.md) to access the last element - [back](back.md) to access the last element
- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view - [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view (document order, not
sorted; duplicate keys are visited)
## Version history ## Version history
+1 -1
View File
@@ -100,7 +100,7 @@ When iterating over an array, `key()` will return the index of the element as st
- [begin](begin.md) returns an iterator to the first element - [begin](begin.md) returns an iterator to the first element
- [end](end.md) returns an iterator to one past the last element - [end](end.md) returns an iterator to one past the last element
- [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`, - [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`,
not `#!cpp const auto&`: items are produced on the fly) not `#!cpp const auto&`: items are produced on the fly; document order, not sorted; duplicate keys are visited)
## Version history ## Version history
@@ -275,8 +275,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
- documentation on [runtime assertions](../../features/assertions.md) - documentation on [runtime assertions](../../features/assertions.md)
- see [`at`](at.md) for access by reference with range checking - see [`at`](at.md) for access by reference with range checking
- see [`value`](value.md) for access with default value - see [`value`](value.md) for access with default value
- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (always - [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (a duplicate
returns a discarded view instead of assuming undefined behavior) key resolves to the first member, whereas `parse()` keeps the last; a missing key gives a discarded view instead of
undefined behavior)
## Version history ## Version history
+19
View File
@@ -112,6 +112,16 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead. [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
!!! tip "Performance"
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
`#!cpp std::istream` comes close: the parser scans the characters its stream buffer holds in bulk, without reading
ahead, so the stream is left exactly where it would be after reading one character at a time. A `#!cpp FILE*` is
read one character at a time, but the file is locked only once, so other threads that use the same `FILE` object
wait until parsing is done. See [input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
[`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md), and
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
## Examples ## Examples
??? example "Example: (1) parse from a character array" ??? example "Example: (1) parse from a character array"
@@ -247,6 +257,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
- [operator>>](../operator_gtgt.md) - deserialize from stream - [operator>>](../operator_gtgt.md) - deserialize from stream
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input - [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
instead of treating it as end of input instead of treating it as end of input
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
at a time
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
functions
## Version history ## Version history
@@ -261,6 +275,11 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
it as end of input; planned to become the default in version 4.0.0. it as end of input; planned to become the default in version 4.0.0.
- Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash - Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash
(`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0. (`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0.
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) restores reading one character at a
time).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -116,6 +116,10 @@ consumer `sax` has a super-linear complexity.
A UTF-8 byte order mark is silently ignored. A UTF-8 byte order mark is silently ignored.
When reading from a `#!c FILE*`, the file is locked while it is parsed. The lock is recursive, so the SAX handler may
use the same file with stdio functions, but it must not wait for another thread that uses the same `FILE` object; see
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
## Examples ## Examples
??? example ??? example
@@ -137,6 +141,10 @@ A UTF-8 byte order mark is silently ignored.
- [parse](parse.md) - deserialize from a compatible input - [parse](parse.md) - deserialize from a compatible input
- [accept](accept.md) - check if the input is valid JSON - [accept](accept.md) - check if the input is valid JSON
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
at a time
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
functions
## Version history ## Version history
@@ -148,6 +156,10 @@ A UTF-8 byte order mark is silently ignored.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0. - Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right - `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
after the parsed value when `strict` is `#!cpp false`. after the parsed value when `strict` is `#!cpp false`.
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -124,3 +124,5 @@ Linear in the size of the JSON value `j`.
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an - Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid BJData. array or object was silently skipped, producing invalid BJData.
- Writes unsigned integers wider than 64 bits as high-precision numbers since version 3.13.0; previously, they were
silently truncated to 64 bits.
+3 -2
View File
@@ -37,8 +37,9 @@ With (2), the bytes written before the exception remain in the output adapter.
## Exceptions ## Exceptions
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer - Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer outside
above 9223372036854775807, which BON8 cannot represent the range of int64 (an unsigned integer above 9223372036854775807, or, with a number type wider than 64 bits, any
integer beyond int64), which BON8 cannot represent
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
valid UTF-8 valid UTF-8
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is - Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
@@ -47,6 +47,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value - Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"` is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains a signed integer
outside the range of int64 or an unsigned integer outside the range of uint64, which is only possible with a number
type wider than 64 bits; example:
`"integer number 9223372036854775808 cannot be represented by BSON as it does not fit int64"`
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains - Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"` a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array, - Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
@@ -119,3 +123,5 @@ pass before anything is written.
that is not valid UTF-8 unchanged, as before; `strict` (the default if that is not valid UTF-8 unchanged, as before; `strict` (the default if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
is written. is written.
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
number type wider than 64 bits were silently truncated.
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
## Exceptions ## Exceptions
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
outside [-2^64, 2^64-1], which is only possible with a number type wider than 64 bits; example:
`"integer number 18446744073709551616 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]"`
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is - Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
not valid UTF-8 and `error_handler` is `strict` (the default only if not valid UTF-8 and `error_handler` is `strict` (the default only if
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
@@ -90,3 +93,5 @@ Linear in the size of the JSON value `j`.
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`. [`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an - Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid CBOR. array or object was silently skipped, producing invalid CBOR.
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
number type wider than 64 bits were silently truncated.
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
## Exceptions ## Exceptions
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
outside [-2^63, 2^64-1], which is only possible with a number type wider than 64 bits; example:
`"integer number 18446744073709551616 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]"`
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary - Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example: value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
`"MessagePack length 4294967296 exceeds maximum of 4294967295"` `"MessagePack length 4294967296 exceeds maximum of 4294967295"`
@@ -112,3 +115,5 @@ Linear in the size of the JSON value `j`.
`number_unsigned_t`. `number_unsigned_t`.
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an - Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
array or object was silently skipped, producing invalid MessagePack. array or object was silently skipped, producing invalid MessagePack.
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
number type wider than 64 bits were silently truncated.
@@ -18,10 +18,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many 1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
were removed; `#!cpp 0` if `object` has no member with this key. were removed; `#!cpp 0` if `object` has no member with this key.
2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`). 2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values 3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md). `ptr` must not be empty
were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object ([`root()`](root.md) itself cannot be erased), and the *parent* of the target must already exist. The target is
member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md) removed as in 1. if the parent is an object (returns the number of removed members, `#!cpp 0` or more) and as in 2.
itself cannot be erased. if it is an array (always returns `#!cpp 1`).
## Template parameters ## Template parameters
@@ -69,10 +69,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
object, or a primitive value the pointer's parent resolves to) throws what 1. throws. object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view Overloads 1. and 2. also throw [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202)
does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a ("view does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md)
view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own view or a view of a *different* document. Overload 3. never throws `invalid_iterator.202`: it takes no view, and it
[`root()`](root.md)). always starts from this document's own [`root()`](root.md).
## Complexity ## Complexity
@@ -92,9 +92,10 @@ view of a *different* document (overloads 1-2 only; overload 3 always starts fro
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the longer appears when `array`/`object` is read, dumped, or iterated. What does change is the container's sequence of
*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator *links* to its elements: removing an element of `array` (2.) shifts the links to the elements after it by one, as
already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout. [`insert`](insert.md) does in the other direction. *Every* iterator already taken over `array`/`object` is invalidated
by an erase, not only those at or after the removed element, since the container may now use a different sequence.
## Examples ## Examples
@@ -110,17 +110,30 @@ enum class image_check
How thoroughly `load()` validates `image` before trusting it. How thoroughly `load()` validates `image` before trusting it.
| value | checks | guarantees | `full` (the default)
|----------|--------------------------------------------------------------------------------------------------------------|------------| : Checks everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a
| `full` | everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a string still in the source text, that it contains no quote, backslash, or control character); and that every number token is well-formed and matches the value stored for it; strings and float tokens may share a range only if the ranges are identical (as nodes that share a value do), and never overlap otherwise | reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document | string still in the source text, that it contains no quote, backslash, or control character); and that every number
| `bounds` | structure and bounds only -- that every offset and count in the node index stays inside the image | reading and serializing stay memory-safe, but a crafted image can hold strings that are not valid UTF-8 or that serialize to invalid JSON ([`dump()`](../basic_json_view/dump.md) writes them unchanged or throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316)), and numbers whose values differ from their text | token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the
| `none` | nothing | images from a trusted source only -- reading a damaged image is undefined behavior | ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a
checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for
an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the
network.
`full` is the default and the right choice for an image from anything you do not fully control -- a file, a cache `bounds`
shared with other processes, a peer on the network. `bounds` skips scanning the text and the decoded strings, so it : Checks structure and bounds only: that every offset and count in the node index stays inside the image. So
fits a cache your own process just wrote and reads straight back, where damage would mean a bug or a hardware fault `load()`, [`root()`](root.md), element access, and [`materialize()`](../basic_json_view/materialize.md) can never
rather than adversarial input; it still cannot crash or read out of bounds. `none` skips validation entirely and read outside the image, even on a damaged one. It does *not* guarantee that the image describes valid JSON: a
should only be used for an image you trust as much as your own memory. string that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8
or invalid JSON, or throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and a number can
read back with a value that does not match how it is spelled. An [editable document](../json_editable_document.md)
does not take such a string over: [`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw
`type_error.316` when they copy it from a view of the loaded document, and leave the editable document unchanged.
`bounds` skips scanning the text and the decoded strings, so it fits a cache your own process just wrote and reads
straight back, where damage would mean a bug or a hardware fault rather than adversarial input.
`none`
: Checks nothing. Reading a damaged image is undefined behavior, so use it only for an image you trust as much as
your own memory.
**Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the **Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
@@ -134,16 +147,6 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is
("unknown format"). Use images to cache a document within one build of the library, or to hand one to another ("unknown format"). Use images to cache a document within one build of the library, or to hand one to another
process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format. process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format.
**What `image_check::bounds` does not guarantee.** A bounds-checked image can never make `load()`,
[`root()`](root.md), element access, or [`materialize()`](../basic_json_view/materialize.md) read outside the image,
so those stay safe on a damaged one. It does *not* guarantee that the image describes valid JSON: a string
that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8 or invalid
JSON, or throw `type_error.316`, and a number can read back with a value that does not match how it is spelled.
Reserve `bounds` for images you already trust to be well-formed, and use it only to skip the extra scan.
An [editable document](../json_editable_document.md) does not take such a string over either:
[`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw `type_error.316` when they copy it from
a view of the loaded document, and leave the editable document unchanged.
## Examples ## Examples
??? example "Caching a document, ownership, and a rejected image" ??? example "Caching a document, ownership, and a rejected image"
+3 -3
View File
@@ -41,7 +41,7 @@ basic_json_view at(const json_pointer& ptr) const;
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -77,8 +77,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the another, in document order, stopping at the first match. Each comparison first checks the
key's length -- already known from the index, without reading the key bytes -- before comparing its content. key's length -- already known from the index, without reading the key bytes -- before comparing its content.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
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). index (unlike `BasicJsonType`'s array, which is random-access).
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
+2 -3
View File
@@ -13,7 +13,7 @@ The last element or member value. For a primitive value (number, string, boolean
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -33,8 +33,7 @@ index.
## Notes ## Notes
Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined behavior for an empty array or object, Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined behavior for an empty array or object,
`back()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and for a `back()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded view.
discarded view, where `BasicJsonType::back()` also throws.
## Examples ## Examples
@@ -6,7 +6,7 @@ iterator begin() const noexcept;
Returns an iterator to the first element of an array, or the first member value of an object, in **document order** Returns an iterator to the first element of an array, or the first member value of an object, in **document order**
-- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one -- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one
element (itself); `#!json null` and a [discarded](is_discarded.md) view iterate as an empty range. element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range.
## Return value ## Return value
@@ -23,7 +23,7 @@ Constant.
## Notes ## Notes
For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md), [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and [`contains`](contains.md),
which all resolve to the *first* member with a given key. See the which all resolve to the *first* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
@@ -31,6 +31,10 @@ Because objects are iterated in document order rather than sorted by key, the or
iterating the [`materialize()`](materialize.md)d `BasicJsonType` value would produce: a `#!cpp basic_json` object iterating the [`materialize()`](materialize.md)d `BasicJsonType` value would produce: a `#!cpp basic_json` object
(`std::map`-backed by default) sorts its keys, while a view does not. (`std::map`-backed by default) sorts its keys, while a view does not.
An `ordered_json` value also keeps document order, but it keeps only one entry per key: as `parse()` does, it keeps the
*last* value, at the position of the key's *first* occurrence. The view, in contrast, visits every member, including
the duplicates.
## Examples ## Examples
??? example ??? example
@@ -35,8 +35,8 @@ No-throw guarantee: this function never throws exceptions.
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after 1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
another, in document order, stopping at the first match. Each comparison first checks the another, in document order, stopping at the first match. Each comparison first checks the
key's length -- already known from the index, without reading the key bytes -- before comparing its content. key's length -- already known from the index, without reading the key bytes -- before comparing its content.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
on average. constant time on average.
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and
[`at`](at.md#complexity) with a JSON pointer. [`at`](at.md#complexity) with a JSON pointer.
@@ -55,7 +55,8 @@ view.
!!! info "Postconditions" !!! info "Postconditions"
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md) and
`#!cpp v.at(key)` does not throw. If
`#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not `#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not
throw. throw.
+17 -21
View File
@@ -6,7 +6,8 @@ size_type count(const char* key) const;
size_type count(const string_t& key) const; size_type count(const string_t& key) const;
``` ```
Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp 0` otherwise. Returns the number of members with key `key` if the value is an object, `#!cpp 0` otherwise. Members with a repeated
key are all counted.
## Parameters ## Parameters
@@ -15,7 +16,8 @@ Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp
## Return value ## Return value
`#!cpp 1` if the value is an object and has a member with key `key`, `#!cpp 0` otherwise. the number of members of the object with key `key` (duplicates included), or `#!cpp 0` if the value is not an object
or has no such member.
## Exception safety ## Exception safety
@@ -23,36 +25,30 @@ No-throw guarantee: this function never throws exceptions.
## Complexity ## Complexity
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, Linear in the number of members: unlike the lookup functions, `count()` does not stop at the first match, because it
in document order, stopping at the first match. Each comparison first checks the key's length has to count every member with the key. Members are compared one after another, in document order. Each comparison
-- already known from the index, without reading the key bytes -- before comparing its content. first checks the key's length -- already known from the index, without reading the key bytes -- before comparing its
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on content. The hash index of the keys of objects with 128 or more members does not help here, since every member
average. is visited.
## Notes ## Notes
!!! warning "Duplicate keys: the first member wins"
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
the last value.
This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md) This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md)
view. view.
Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value can in principle exceed `#!cpp 1` for If the source text repeats a key, `count()` returns more than `#!cpp 1` for it, even though the
an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly [materialized](materialize.md) value (like the result of `parse()`) would have only one member with that key. Hence
[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the `#!cpp count(key) > 1` detects duplicate keys. This is unlike the lookup functions
[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require ([`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md)), which resolve to the
counting every member with a matching key (the lookup functions resolve to the *first* one). *first* member; see the [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
## Examples ## Examples
??? example ??? example
The example below validates that every transaction of a batch carries a mandatory `amount` field, using The example below validates that every transaction of a batch carries exactly one `amount` field, using
`count()` before deciding whether to materialize a transaction at all. `count()` before deciding whether to materialize a transaction at all. It reports a transaction with no `amount`
field and one with a duplicated `amount` field.
```cpp ```cpp
--8<-- "examples/basic_json_view__count.cpp" --8<-- "examples/basic_json_view__count.cpp"
+4 -2
View File
@@ -60,11 +60,13 @@ Linear in the size of the output text.
The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by
the call stack -- as for [`materialize()`](materialize.md). the call stack -- as for [`materialize()`](materialize.md).
Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). With Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md).
`#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion,
With `#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion,
exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and
integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too -- integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too --
except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it. except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it.
`#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception `#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception
for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more
digits than any number type holds (such a literal is itself classified as a float, see digits than any number type holds (such a literal is itself classified as a float, see
+2 -3
View File
@@ -5,9 +5,8 @@ iterator end() const noexcept;
``` ```
Returns an iterator to one past the last element of an array, one past the last member value of an object, in Returns an iterator to one past the last element of an array, one past the last member value of an object, in
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself); **document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a
`#!json null` and a [discarded](is_discarded.md) view iterate as an empty range, so `#!cpp begin() == end()` for `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range (`#!cpp begin() == end()`).
them.
## Return value ## Return value
+2 -2
View File
@@ -28,8 +28,8 @@ No-throw guarantee: this function never throws exceptions.
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another, Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
in document order, stopping at the first match. Each comparison first checks the key's length in document order, stopping at the first match. Each comparison first checks the key's length
-- already known from the index, without reading the key bytes -- before comparing its content. -- already known from the index, without reading the key bytes -- before comparing its content.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
average. constant time on average.
## Notes ## Notes
@@ -13,7 +13,7 @@ The first element or member value. For a primitive value (number, string, boolea
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -31,8 +31,8 @@ Constant.
## Notes ## Notes
Unlike [`BasicJsonType::front()`](../basic_json/front.md), which has undefined behavior for an empty array or Unlike [`BasicJsonType::front()`](../basic_json/front.md), which has undefined behavior for an empty array or
object, `front()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and object, `front()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded
for a discarded view, where `BasicJsonType::front()` also throws. view.
## Examples ## Examples
+10 -7
View File
@@ -10,8 +10,8 @@ Converts the value to `T`.
For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it: For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it:
- `#!cpp bool` - `#!cpp bool`
- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as - arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
[`BasicJsonType::get<T>()`](../basic_json/get.md) converts a boolean) [`BasicJsonType::get<T>()`](../basic_json/get.md))
- `#!cpp std::nullptr_t` - `#!cpp std::nullptr_t`
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string - `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string
- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's - [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's
@@ -19,10 +19,10 @@ For the types below, the conversion works directly on the flat index -- no `Basi
document's own buffer of decoded strings (see [`get_string()`](get_string.md)) document's own buffer of decoded strings (see [`get_string()`](get_string.md))
- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md) - `BasicJsonType` -- equivalent to [`materialize()`](materialize.md)
- `basic_json_view` -- returns `#!cpp *this` - `basic_json_view` -- returns `#!cpp *this`
- `#!cpp std::vector<U, A>` -- element by element, each converted with `#!cpp get<U>()`; `#!cpp - `#!cpp std::vector<U, A>` -- from an array, element by element, each converted with `#!cpp get<U>()`; `#!cpp
std::vector<basic_json_view>` keeps a view of every element instead of a value std::vector<basic_json_view>` keeps a view of every element instead of a value
- `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp - `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp
(const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get<V>()`; with a repeated (const char*, std::size_t)` pair -- from an object, member by member, each value converted with `#!cpp get<V>()`; with a repeated
key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and
[`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members [`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members
instead of values instead of values
@@ -43,7 +43,7 @@ the value, converted to `T`
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -62,6 +62,9 @@ None of the exceptions thrown directly by this function (the first three bullets
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled. `BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
document.
## Complexity ## Complexity
@@ -73,8 +76,8 @@ exception thrown while converting through `materialize()` (the last bullet) is d
- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's - `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's
own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp
std::unordered_map`), times the complexity of converting one member to the mapped type. std::unordered_map`), times the complexity of converting one member to the mapped type.
- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of - every other `T`: the cost of [`materialize()`](materialize.md) and of destroying the `BasicJsonType` value
[`BasicJsonType::get<T>()`](../basic_json/get.md) on it. afterwards, plus that of [`BasicJsonType::get<T>()`](../basic_json/get.md).
## Notes ## Notes
@@ -15,7 +15,7 @@ had to be decoded once when the document was parsed).
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -37,9 +37,14 @@ hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get
zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest
counterpart, except that it returns a view instead of a reference to a value that must already exist. counterpart, except that it returns a view instead of a reference to a value that must already exist.
`get_string()` borrows the string: nothing is copied, whereas [`get<std::string>()`](get.md) copies it. For a borrowed
document, the returned [`string_view_t`](index.md#member-types) is valid as long as the caller's buffer; for an owned
one, as long as the document.
The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the
[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's [validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's
source text. source text, which for a borrowed document must remain valid **and unmodified** for as long as the document exists,
since floating-point values and unescaped strings are read from the source lazily.
## Examples ## Examples
+23 -8
View File
@@ -5,12 +5,15 @@ template<typename T>
T& get_to(T& v) const; T& get_to(T& v) const;
``` ```
Converts the value to `T` and assigns it to `v`. Equivalent to Converts the value to `T` and stores it in `v`, in one of two ways:
```cpp - For the types that [`get<T>()`](get.md) converts directly -- arithmetic types, `#!cpp bool`,
v = get<T>(); `#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`,
return v; `basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is
``` assigned: `#!cpp v = get<T>()`.
- For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is
called, i.e. `from_json()` is invoked with the existing `v`, exactly as
[`BasicJsonType::get_to()`](../basic_json/get_to.md) does.
## Template parameters ## Template parameters
@@ -28,15 +31,27 @@ return v;
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, `v` is not modified. - For the directly converted types: strong exception safety. If an exception is thrown, `v` is not modified.
- For every other type: as for [`BasicJsonType::get_to()`](../basic_json/get_to.md). `materialize()` throws before `v`
is touched, but a `from_json()` that throws may leave `v` partly modified.
## Exceptions ## Exceptions
Whatever [`get<T>()`](get.md) throws for the same value and `T`. - For the directly converted types: whatever [`get<T>()`](get.md) throws for the same value and `T`.
- For every other type: whatever [`materialize()`](materialize.md) and
[`BasicJsonType::get_to()`](../basic_json/get_to.md) throw.
## Complexity ## Complexity
Whatever [`get<T>()`](get.md) has for the same `T`. - For the directly converted types: whatever [`get<T>()`](get.md) has for the same `T`.
- For every other type: that of [`materialize()`](materialize.md), plus that of
[`BasicJsonType::get_to()`](../basic_json/get_to.md).
## Notes
The difference between the two ways matters for a `from_json()` that sets only the fields present in the JSON: since
it runs on the existing `v`, the other fields of `v` keep their values, whereas assigning `get<T>()` would replace
them with those of a default-constructed `T`.
## Examples ## Examples
+17 -9
View File
@@ -14,18 +14,27 @@ to the document and a pointer into its index), trivially copyable. A view is val
- the document has not been re-parsed with [`read()`](../basic_json_document/read.md) (or - the document has not been re-parsed with [`read()`](../basic_json_document/read.md) (or
[`parse()`](../basic_json_document/parse.md) into it) or shrunk with [`parse()`](../basic_json_document/parse.md) into it) or shrunk with
[`shrink_to_fit()`](../basic_json_document/shrink_to_fit.md) since the view was taken, and [`shrink_to_fit()`](../basic_json_document/shrink_to_fit.md) since the view was taken, and
- if the document borrows its source text, that text is still alive. - if the document borrows its source text, that text is still alive and unmodified (floating-point values and
unescaped strings are read from it lazily).
Moving the document itself does not invalidate its views: the index is heap-allocated independently of the Moving the document itself does not invalidate its views: the index is heap-allocated independently of the
`basic_json_document` object. `basic_json_document` object.
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element `basic_json_view` provides the read-only part of the `BasicJsonType` interface:
access, lookup, iteration, conversion, and comparison -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a - the type-inspection functions
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and - element access
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). [`operator==`](operator_eq.md) and - lookup
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a - iteration
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided. - conversion: [`get<T>()`](get.md), [`get_string()`](get_string.md), [`number_token()`](number_token.md), and
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand
- comparison: [`operator==`](operator_eq.md) and [`operator!=`](operator_ne.md) compare two views, or a view and a
`BasicJsonType` value, without ever building a `BasicJsonType` value for a view; no ordering comparison
(`#!cpp operator<`) is provided
[`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and [`value`](value.md) also accept a
[`json_pointer`](../json_pointer/index.md). Unlike `basic_json`, there are no implicit conversion operators (no
`#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md).
`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an `basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an
**editable** document (`#!cpp Editable == true`) sees every edit made through **editable** document (`#!cpp Editable == true`) sees every edit made through
@@ -61,7 +70,6 @@ still refers to it -- including ones taken before the change -- reads the new va
- **string_view_t** - `#!cpp std::string_view` on C++17 and newer, a minimal internal substitute otherwise - **string_view_t** - `#!cpp std::string_view` on C++17 and newer, a minimal internal substitute otherwise
- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an - **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an
object, in document order; both names refer to the same type, since a view is always read-only object, in document order; both names refer to the same type, since a view is always read-only
- **item** - a (key, value) pair produced by [`items()`](items.md)
- [**number_format**](number_format.md) - how [`dump()`](dump.md) writes numbers - [**number_format**](number_format.md) - how [`dump()`](dump.md) writes numbers
## Member functions ## Member functions
@@ -4,9 +4,9 @@
/* unspecified */ items() const noexcept; /* unspecified */ items() const noexcept;
``` ```
Returns a range of [`item`](index.md#member-types) values -- (key, value) pairs -- for use in range-based for loops. Returns a range of items -- (key, value) pairs -- for use in range-based for loops. An item's `#!cpp key()` is a
The key of an array element is its index, converted to a string, as for `#!cpp string_view_t` (for an array element, its index converted to a string, as for
[`BasicJsonType::items()`](../basic_json/items.md). [`BasicJsonType::items()`](../basic_json/items.md)), and its `#!cpp value()` is a `basic_json_view` of the element.
The returned type is not part of the public API and may change between versions; use a range-based for loop (see the The returned type is not part of the public API and may change between versions; use a range-based for loop (see the
example), or `#!cpp decltype(v.items())` if you need to name it. example), or `#!cpp decltype(v.items())` if you need to name it.
@@ -33,8 +33,8 @@ is nothing for a reference to bind to.
## Return value ## Return value
A range whose iterators dereference to [`item`](index.md#member-types) and whose `#!cpp begin()`/`#!cpp end()` are A range whose iterators dereference to an item (with `#!cpp key()` and `#!cpp value()`, see above) and whose
equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order. `#!cpp begin()`/`#!cpp end()` are equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order.
## Exception safety ## Exception safety
@@ -48,14 +48,14 @@ Constant.
As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all
occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md),
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the and [`contains`](contains.md), which resolve to the *first* member with a given key. See the
[Notes on duplicate keys](operator[].md#notes) of `operator[]`. [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
!!! danger "Lifetime issues" !!! danger "Lifetime issues"
As for `BasicJsonType::items()`, calling `items()` on a temporary view (or a temporary document) is dangerous: Unlike `BasicJsonType::items()` (see [#2040](https://github.com/nlohmann/json/issues/2040)), calling `items()` on
the range refers back to the document, so the document must outlive the loop. See a temporary view is safe, for instance `#!cpp for (auto item : v["a"].items())`: the returned range holds a copy
[#2040](https://github.com/nlohmann/json/issues/2040) for the `BasicJsonType` background. of the view. What must outlive the loop is the document and, for a borrowed document, the source buffer.
## Examples ## Examples
@@ -15,7 +15,7 @@ and [`materialize()`](materialize.md) can only represent approximately, as a `nu
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -50,7 +50,7 @@ basic_json_view operator[](const json_pointer& ptr) const;
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -83,8 +83,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
another, in document order, stopping at the first match. Each comparison first checks the another, in document order, stopping at the first match. Each comparison first checks the
key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a
key of a different length than `key` is rejected without touching the source text. key of a different length than `key` is rejected without touching the source text.
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
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). index (unlike `BasicJsonType`'s array, which is random-access).
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
@@ -110,13 +110,13 @@ document.
!!! warning "Duplicate keys: the first member wins" !!! warning "Duplicate keys: the first member wins"
If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md), If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md),
[`contains`](contains.md), [`count`](count.md), [`value`](value.md), and JSON pointer resolution) all resolve to [`contains`](contains.md), [`value`](value.md), and JSON pointer resolution) all resolve to
the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every [`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every
member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use `#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match. [`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
The hash index of a larger object (128 members or more) leads to the first member of a key as well. The key hash index of a larger object (128 members or more) leads to the first member of a key as well.
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including [`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and
[`size()`](size.md#notes). [`size()`](size.md#notes).
@@ -14,7 +14,7 @@ same string for a discarded `BasicJsonType` value).
a string representation of the type ([`value_t`](../basic_json/value_t.md)): a string representation of the type ([`value_t`](../basic_json/value_t.md)):
| Value type | return value | | Value type | return value |
|-----------------------------------------------------|---------------| |----------------------------------------------------|---------------|
| `#!json null` | `"null"` | | `#!json null` | `"null"` |
| boolean | `"boolean"` | | boolean | `"boolean"` |
| string | `"string"` | | string | `"string"` |
@@ -44,7 +44,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
## Exception safety ## Exception safety
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to. Strong guarantee: there are no changes to the view or the document it refers to.
## Exceptions ## Exceptions
@@ -70,8 +70,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after 1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
another, in document order, stopping at the first match. Plus the complexity of converting another, in document order, stopping at the first match. Plus the complexity of converting
the found member to `T` (see [`get`](get.md)). the found member to `T` (see [`get`](get.md)).
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
on average. constant time on average.
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at 2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value [`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
+4
View File
@@ -16,6 +16,10 @@ header. See also the [macro overview page](../../features/macros.md).
## Parsing ## Parsing
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
instead of scanning its stream buffer in bulk
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with `std::fgetc` instead of
locking it once and using the unlocked stdio functions
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned - [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
right after a parsed number right after a parsed number
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR, - [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
@@ -0,0 +1,144 @@
# JSON_DISABLE_STREAM_BULK_SCAN
```cpp
#define JSON_DISABLE_STREAM_BULK_SCAN /* value */
```
When defined to `1`, parsing from a `#!cpp std::istream` reads the input one character at a time through the stream
buffer, as in version 3.12.0 and earlier, instead of scanning the characters that the stream buffer already holds in
bulk.
The macro is an escape hatch for stream buffers that do not follow the usual contract of `std::basic_streambuf` (see
the requirements on the stream buffer in the notes below). It does not change any result: values, exceptions, and the
position of the stream after parsing are the same either way. Only the speed differs.
## Default definition
The default value is `0`: streams are scanned in bulk.
```cpp
#define JSON_DISABLE_STREAM_BULK_SCAN 0
```
## Notes
!!! note "How streams are scanned"
A `std::streambuf` keeps the part of the input it has read but not yet handed out in its *get area*, the range
from `gptr()` to `egptr()`. A `std::filebuf`, for instance, reads the file in blocks of a few kilobytes into its get
area, and a `std::stringbuf` exposes its whole string as one get area.
When parsing from a `std::istream`, the parser scans this get area directly, with the same fast paths that it uses
for a `std::string`: runs of ordinary string characters, numbers, and indentation are recognized several bytes at a
time instead of being requested from the stream buffer one character at a time. When the parser reaches the end of
the get area, it asks the stream buffer for the next character as before, which makes the stream buffer refill its
get area.
The parser only reads characters that it would also have read one at a time, and it advances the stream buffer
exactly over the characters it consumes. Nothing is read ahead, buffered elsewhere, or put back. Therefore, the
stream is left exactly where it was left before:
- [`operator>>`](../operator_gtgt.md) and [`sax_parse`](../basic_json/sax_parse.md) with `strict = false` can still
read several values from one stream, and the stream can still be used with other `std::istream` functions after a
value has been read.
- With [`JSON_PRECISE_STREAM_POSITION`](json_precise_stream_position.md), the stream is still positioned right after
a parsed number.
- Exception messages, including the line, the column, and the last read token, are unchanged.
!!! note "Performance"
Parsing from a `std::ifstream` or `std::istringstream` is typically 1.5 to 2 times as fast as before, and reading
many small values from one stream with `operator>>` 1.4 to 4 times as fast. Parsing from a stream is then within
about 10 to 20 % of first reading the whole file into a `std::string` and parsing that.
A stream buffer without a get area (one that only overrides `underflow()` and `uflow()` and hands out every
character from there) offers nothing to scan in bulk, so it is read one character at a time as before.
!!! note "Requirements on the stream buffer"
The bulk scan relies on the following behavior, which every stream buffer of the standard library and every stream
buffer that keeps its input in its get area has:
- When `sgetc()` returns a character while `gptr() < egptr()`, that character is `*gptr()`; when `sbumpc()` returns
one, it was at `gptr()` before the call.
- Characters in the get area are not changed while they are in the get area.
- `underflow()` or `uflow()` either sets up a get area that holds the returned character at `gptr()` (or at
`gptr() - 1` after `uflow()`), or leaves the get area empty.
A stream buffer that returns characters from `uflow()` while leaving unrelated characters in its get area, or that
changes the characters in its get area in place, does not meet these requirements. Define the macro to `1` for
such stream buffers.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dsbs`), resulting in
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
!!! note "Other inputs"
Strings, string views, vectors, arrays, and pointer or iterator ranges over contiguous memory are scanned in bulk
anyway. Reading from a `#!cpp FILE*` is not affected by this macro; see
[`JSON_DISABLE_UNLOCKED_FILE_IO`](json_disable_unlocked_file_io.md) for that input.
## Examples
??? example "Example: reading values from a stream (default)"
The stream is scanned in bulk, and the values are read one after the other:
```cpp
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
json j1, j2, j3;
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
std::string word;
input >> word; // "rest"
}
```
??? example "Example: reading one character at a time (macro defined to 1)"
With the macro, the same program reads the stream one character at a time, with the same results:
```cpp
#define JSON_DISABLE_STREAM_BULK_SCAN 1
#include <iostream>
#include <sstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
json j1, j2, j3;
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
std::string word;
input >> word; // "rest"
}
```
## See also
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - leave an input stream positioned right after a
parsed number
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
functions
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
## Version history
- Added in version 3.13.0.
@@ -0,0 +1,115 @@
# JSON_DISABLE_UNLOCKED_FILE_IO
```cpp
#define JSON_DISABLE_UNLOCKED_FILE_IO /* value */
```
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
Only the speed and the locking of the file differ (see the notes on locking below).
Builds with MemorySanitizer use `std::fgetc` automatically, because the inline `getc_unlocked` of glibc reads the stdio
buffer directly, which glibc (not instrumented) filled, and MemorySanitizer would report it as uninitialized.
## Default definition
The default value is `0`: the file is locked once and read with the unlocked stdio functions where they are available.
```cpp
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
```
## Notes
!!! note "Background"
The C standard library makes every `std::fgetc` call thread-safe by locking and unlocking the `FILE` object. When
parsing from a `FILE*`, the parser reads one character at a time, so this happens for every character of the input.
Instead, the parser locks the file once when parsing starts and unlocks it when parsing ends - also when an
exception is thrown - and reads the characters in between without locking:
| C library | functions |
|------------------------------------------------------------|----------------------------------------------------|
| glibc (Linux), Apple (macOS, iOS), Android, FreeBSD, NetBSD, OpenBSD, DragonFly BSD | `flockfile`, `getc_unlocked`, `funlockfile` (POSIX) |
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
!!! note "Locking"
While a value is parsed from a `FILE*`, the parsing thread holds the file's lock. The lock is recursive, so the
parsing thread itself - for instance, a [SAX](../json_sax/index.md) handler or a
[parser callback](../basic_json/parser_callback_t.md) - can keep using the file with stdio functions such as
`std::ftell`. Other threads that use the same `FILE` object wait until the parser is done. Before, they could
interleave their reads with the parser's, which made the parsed input unpredictable anyway.
A program in which a SAX handler or parser callback waits for **another** thread that uses the **same** `FILE`
object would deadlock. Define the macro to `1` for such programs.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dufio`), resulting in
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
!!! note "Other inputs"
Reading from a `#!cpp std::istream` is not affected by this macro; see
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) for that input.
## Examples
??? example "Example: parsing from a `FILE*` (default)"
The file is locked once while it is parsed:
```cpp
#include <cstdio>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::FILE* f = std::fopen("example.json", "rb");
json j = json::parse(f); // f is locked until parse() returns
std::fclose(f);
}
```
??? example "Example: locking for every character (macro defined to 1)"
With the macro, every character is read with `std::fgetc`, with the same result:
```cpp
#define JSON_DISABLE_UNLOCKED_FILE_IO 1
#include <cstdio>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
std::FILE* f = std::fopen("example.json", "rb");
json j = json::parse(f); // std::fgetc for every character
std::fclose(f);
}
```
## See also
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a
time
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
## Version history
- Added in version 3.13.0.
@@ -72,6 +72,12 @@ The default value is `0` (disabled — existing behavior is preserved).
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types. without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
!!! note "Bulk scanning"
The parser scans the characters in the stream buffer in bulk (see
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)), but it never reads ahead. The stream is left
at the same position with and without bulk scanning, in both modes of this macro.
!!! tip "Workaround without the macro" !!! tip "Workaround without the macro"
Separate the values in the stream with whitespace. The character consumed after a number is then the separator, Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
@@ -12,8 +12,9 @@ non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
same input is accepted or rejected either way, and every parse error is reported at the same position with the same same input is accepted or rejected either way, and every parse error is reported at the same position with the same
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the diagnostic is preserved). A `std::istream` uses simdutf for the characters its stream buffer holds (see
scalar path. [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other streaming inputs (`FILE*`, wide strings,
user-defined adapters) always use the scalar path.
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
simdutf library. When it is not defined, no simdutf header is included and there is no dependency. simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
@@ -14,10 +14,12 @@ The same input is accepted or rejected either way, with the same values, and err
the speed differs. The macro exists for platforms whose compilers lack the intrinsics headers, and to test the portable the speed differs. The macro exists for platforms whose compilers lack the intrinsics headers, and to test the portable
code. code.
!!! warning "Define consistently" !!! note "ABI compatibility"
The macro selects between two definitions of the same inline functions. It must therefore be defined identically for The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the
**every** translation unit that includes `<nlohmann/json_view.hpp>`; prefer a compile definition on the target. [namespace](../../features/namespace.md) (tag `_vns`), resulting in distinct symbol names. Translation units
compiled with and without it can therefore be linked into the same program without One Definition Rule (ODR)
violations, but they cannot exchange instances of library types.
## Default definition ## Default definition
@@ -17,12 +17,12 @@ Define the macro only together with a compiler option that enables SSSE3 (e.g. `
has it), and only for programs that run on such CPUs. It saves the check of the CPU, which costs little. The same has it), and only for programs that run on such CPUs. It saves the check of the CPU, which costs little. The same
input is accepted or rejected either way; only the speed of non-ASCII text differs. input is accepted or rejected either way; only the speed of non-ASCII text differs.
!!! warning "Define consistently" !!! note "ABI compatibility"
The macro selects between two definitions of the same inline functions. It must therefore be defined identically, The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the
with the same compiler options, for **every** translation unit that includes `<nlohmann/json_view.hpp>`; mixing [namespace](../../features/namespace.md) (tag `_vssse3`, on every architecture), resulting in distinct symbol
translation units that define it with ones that do not is an ODR violation. Prefer a compile definition on the names. Translation units compiled with and without it can therefore be linked into the same program without One
target. Definition Rule (ODR) violations, but they cannot exchange instances of library types.
## Default definition ## Default definition
@@ -39,5 +39,6 @@ the library.
## Version history ## Version history
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in - Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, and `_ekmo` in version 3.13.0; see version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_vns`, and
`_vssse3` in version 3.13.0; see
[`nlohmann` Namespace](../../features/namespace.md#structure). [`nlohmann` Namespace](../../features/namespace.md#structure).
+4
View File
@@ -121,6 +121,7 @@ being read.
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input - [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
instead of treating it as end of input instead of treating it as end of input
- [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number - [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number
- [`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) - read the stream one character at a time
## Version history ## Version history
@@ -133,3 +134,6 @@ being read.
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0. crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed - Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
value if parsing throws. value if parsing throws.
- Scans the characters in the stream buffer of `i` in bulk in version 3.13.0; the position of `i` after the call is
unchanged ([`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) restores reading one character
at a time).
@@ -135,6 +135,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
headers are included. headers are included.
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see - [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)). [`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
to make sure it does not use them as identifiers (see
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
- [x] The library is compiled with exceptions disabled to support alternative means of error handling. - [x] The library is compiled with exceptions disabled to support alternative means of error handling.
## Stable public API ## Stable public API
@@ -5,13 +5,14 @@ using json_document = nlohmann::json_document;
int main() int main()
{ {
// validate that every transaction of a batch carries a mandatory // validate that every transaction of a batch carries exactly one
// "amount" field before materializing any of them into a nlohmann::json // "amount" field before materializing any of them into a nlohmann::json
// value -- count() returns 0 or 1 for an object // value -- count() counts every member with the key
json_document batch = json_document::parse(R"( json_document batch = json_document::parse(R"(
[ [
{"id": 1, "amount": 9.99}, {"id": 1, "amount": 9.99},
{"id": 2} {"id": 2},
{"id": 3, "amount": 5.00, "amount": 500.00}
] ]
)"); )");
@@ -19,9 +20,10 @@ int main()
for (std::size_t i = 0; i < transactions.size(); ++i) for (std::size_t i = 0; i < transactions.size(); ++i)
{ {
const auto transaction = transactions[i]; const auto transaction = transactions[i];
if (transaction.count("amount") == 0) const auto amounts = transaction.count("amount");
if (amounts != 1)
{ {
std::cout << "transaction " << i << " is missing \"amount\"\n"; std::cout << "transaction " << i << " has " << amounts << " \"amount\" fields\n";
continue; continue;
} }
std::cout << "transaction " << i << ": " << transaction["amount"].materialize().dump() << '\n'; std::cout << "transaction " << i << ": " << transaction["amount"].materialize().dump() << '\n';
@@ -1,2 +1,3 @@
transaction 0: 9.99 transaction 0: 9.99
transaction 1 is missing "amount" transaction 1 has 0 "amount" fields
transaction 2 has 2 "amount" fields
+2 -2
View File
@@ -14,8 +14,8 @@ C++ types, and finally serialize it again.
[parsing untrusted input](parsing/untrusted_input.md). [parsing untrusted input](parsing/untrusted_input.md).
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree; - [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
strings and numbers stay in the input and are only decoded when needed. strings and numbers stay in the input and are only decoded when needed.
[Editable documents](json_view.md#editing-a-document) can also be modified, and [images](json_view.md#images) load a - [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
parsed document again without parsing it. - [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar. - [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values ## Accessing and modifying values
+10 -9
View File
@@ -67,9 +67,10 @@ table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a
!!! warning "A borrowed document depends on your buffer" !!! warning "A borrowed document depends on your buffer"
If a document borrows its text, that text **must outlive the document** (and every view taken from it). Reading If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
or writing through a view after the underlying buffer is gone is undefined behavior, exactly as it would be for stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
a dangling `#!cpp std::string_view`. read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
exactly as it would be for a dangling `#!cpp std::string_view`.
A view is valid only while all of the following hold: A view is valid only while all of the following hold:
@@ -77,7 +78,7 @@ A view is valid only while all of the following hold:
- the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md) - the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md)
or [`parse()`](../api/basic_json_document/parse.md) into it), or [`parse()`](../api/basic_json_document/parse.md) into it),
- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and - the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and
- if the document borrows its source text, that text is still alive. - if the document borrows its source text, that text is still alive and unmodified.
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md) allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md)
@@ -167,10 +168,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md), If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md), [`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md), [`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md), and JSON pointer
[`count`](../api/basic_json_view/count.md), and JSON pointer resolution all return the **first** member with that resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last** value
key. `basic_json::parse()` instead keeps the **last** value of a repeated key, so for `#!cpp {"a":1,"a":2}`, of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is
`#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is `#!cpp 2`. `#!cpp 2`. [`count`](../api/basic_json_view/count.md) counts every member with the key, so it is `#!cpp 2` here.
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and [`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and [`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
@@ -179,7 +180,7 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup **Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
to scan all members of the object, even when the key is found at the very first one: this made lookups in small to scan all members of the object, even when the key is found at the very first one: this made lookups in small
objects 1.6 to 3.4 times slower. The hash index of objects with 128 members or more leads to the first member of a objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first member of a
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
duplicates is unpredictable, so neither choice is wrong. duplicates is unpredictable, so neither choice is wrong.
+16
View File
@@ -133,6 +133,14 @@ When defined, default parse and serialize functions for enums are excluded and h
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
## `JSON_DISABLE_STREAM_BULK_SCAN`
When defined to `1`, parsing from a `#!cpp std::istream` reads one character at a time instead of scanning the
characters that the stream buffer holds in bulk. Results are the same either way; the macro is an escape hatch for
stream buffers that do not follow the usual `std::streambuf` contract. The default value is `0`.
See [full documentation of `JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md).
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` ## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
@@ -141,6 +149,14 @@ is planned to become the default in version 4.0.0.
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
## `JSON_DISABLE_UNLOCKED_FILE_IO`
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, which locks the file each
time, instead of locking the file once and reading it with the unlocked stdio functions. Results are the same either
way. The default value is `0`.
See [full documentation of `JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md).
## `JSON_NO_AUTOMATIC_UDLS` ## `JSON_NO_AUTOMATIC_UDLS`
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
+12 -2
View File
@@ -23,6 +23,12 @@ The complete default namespace name is derived as follows:
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`. - [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero - [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
appends `_ekmo`. appends `_ekmo`.
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) defined non-zero appends
`_dsbs`.
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
`_dufio`.
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by - The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component) underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
below. below.
@@ -105,5 +111,9 @@ follows:
- Added ABI tags `_bics` ([`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md)), `_psp` - Added ABI tags `_bics` ([`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md)), `_psp`
([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul` ([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul`
([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8` ([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8`
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), and `_ekmo` ([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)) in version 3.13.0. ([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_vns`
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
@@ -55,6 +55,30 @@ whitespace, because `operator>>` consumes the character that terminates a number
[`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the [`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples. [`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
## Input sources and performance
All inputs produce the same values and the same exceptions, but they are read in different ways, which makes some of
them faster than others:
| input | how it is read |
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
`#!cpp std::string` first and parsing that, and it needs no memory for a copy of the file. The stream is never read
ahead: after parsing, it is positioned exactly as if every character had been read one at a time, so
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
[JSON Lines](json_lines.md)).
Two macros switch these optimizations off without changing any result:
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
follow the usual `std::streambuf` contract, and
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
thread must use the same `#!cpp FILE` object while it is parsed.
## SAX vs. DOM parsing ## SAX vs. DOM parsing
The library offers two parsing models: The library offers two parsing models:
+26 -19
View File
@@ -20,32 +20,39 @@ below apply on every supported standard. This adapter exposes two optimizations
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is - it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
read, and read, and
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one - the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
character (and one function call) at a time. a time, rather than one character (and one function call) at a time.
A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g. [`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
additionally requires contiguous storage. locks the file once per parse instead of once per character. See
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
parsing an equivalent `#!cpp std::string`.
Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a `#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
file, that means reading it into a string first and then parsing the string, rather than passing a iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization: optimization but not the second, since the byte-scanning fast path requires contiguous storage.
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
into a string first is no longer necessary for speed:
```cpp ```cpp
// gets the contiguous fast paths // fast: the stream buffer's get area is scanned in bulk
std::ifstream f("example.json"); std::ifstream f("example.json");
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); json j = json::parse(f);
json j = json::parse(contents);
// does not: input_stream_adapter has no fast path
std::ifstream f2("example.json");
json j2 = json::parse(f2);
``` ```
Two macros switch the stream optimizations off without changing any result:
[`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not follow
the usual `#!cpp std::streambuf` contract, and
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) for programs in which another thread
must use the same `#!cpp FILE` object while it is parsed.
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters) the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
+6 -4
View File
@@ -194,9 +194,9 @@ packet-beta
| Bytes | Field | Type | Contents | | Bytes | Field | Type | Contents |
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------| |-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) | | 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below). There is no node for a discarded value: a [discarded](../api/basic_json_view/is_discarded.md) view has none |
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) | | 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bit 3: moved, bit 4: new, bit 5: linked (see below) |
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 | | 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object | | 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements | | 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself | | 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
@@ -212,7 +212,7 @@ packet-beta
step from element to element this way and skip whole subtrees in constant time. step from element to element this way and skip whole subtrees in constant time.
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below - **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
2^32 for positions one scanner step past the end of the text; `out_of_range.416`). 2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
- **Large objects** (128 members or more) get a hash index after parsing - **Large objects** (128 members or more) get a hash index for their keys after parsing
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)): ([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
not compare every key. Of duplicate keys the table leads to the first, as a linear search does. The object's `extra` not compare every key. Of duplicate keys the table leads to the first, as a linear search does. The object's `extra`
@@ -257,6 +257,8 @@ edited:
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
8-15 hold the address of the value's node, so values never move. 8-15 hold the address of the value's node, so values never move.
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text. - A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
- A value that an entry of such a sequence links to gets the flag *linked* (bit 5): its extent in the parsed layout
(`next`) no longer matters, so a scalar can replace an array or object there in place.
- Views of read-only documents compile without any of this: how views walk the index is a template parameter - Views of read-only documents compile without any of this: how views walk the index is a template parameter
(`navigation<Editable>`). (`navigation<Editable>`).
+20 -4
View File
@@ -972,9 +972,19 @@ double-precision number when `number_float_t` is `#!cpp float`.
### json.exception.out_of_range.407 ### json.exception.out_of_range.407
An unsigned integer number greater than 9223372036854775807 cannot be represented in the binary format. This exception An integer number cannot be represented by the binary format it is serialized to:
is only thrown by [`to_bon8`](../api/basic_json/to_bon8.md), as [BON8](../features/binary_formats/bon8.md) has no
unsigned 64-bit integer type. - [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807.
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
[`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) wider than 64 bits (e.g., `__int128`) can hold values
outside the range of the format: [-2^64, 2^64-1] for CBOR, [-2^63, 2^64-1] for MessagePack, and the range of int64
(signed integers) or uint64 (unsigned integers) for BSON.
[UBJSON](../features/binary_formats/ubjson.md) and [BJData](../features/binary_formats/bjdata.md) never throw this
exception, because they serialize integers beyond 64 bits as high-precision numbers.
!!! note "Former occurrences" !!! note "Former occurrences"
@@ -983,11 +993,17 @@ unsigned 64-bit integer type.
- Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers. - Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers. - Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
!!! failure "Example message" Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
truncated unsigned integers wider than 64 bits.
!!! failure "Example messages"
``` ```
integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64 integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64
``` ```
```
integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]
```
### json.exception.out_of_range.408 ### json.exception.out_of_range.408
+2
View File
@@ -370,7 +370,9 @@ nav:
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md - 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md - 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md - 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
- 'JSON_DISABLE_STREAM_BULK_SCAN': api/macros/json_disable_stream_bulk_scan.md
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md - 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
- 'JSON_DISABLE_UNLOCKED_FILE_IO': api/macros/json_disable_unlocked_file_io.md
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md - 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md - 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
+40 -4
View File
@@ -54,6 +54,14 @@
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0 #define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif #endif
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
#define JSON_DISABLE_STREAM_BULK_SCAN 0
#endif
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
#endif
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else #else
@@ -102,14 +110,38 @@
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS #define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif #endif
#if JSON_DISABLE_STREAM_BULK_SCAN
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
#else
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
#endif
#if JSON_DISABLE_UNLOCKED_FILE_IO
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
#else
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
#endif
#ifdef JSON_VIEW_NO_SIMD
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
#else
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD
#endif
#ifdef JSON_VIEW_USE_SSSE3
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3
#else
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif #endif
// Construct the namespace ABI tags component // Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h #define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \ #define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
#define NLOHMANN_JSON_ABI_TAGS \ #define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -120,7 +152,11 @@
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \ NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \ NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS) NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
// Construct the namespace version component // Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -1342,14 +1342,32 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
return end + (three ? 5 : 4); return end + (three ? 5 : 4);
} }
/// the powers of ten up to 10^16 /// the powers of ten up to 10^19 (all that fit in std::uint64_t)
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept inline const std::array<std::uint64_t, 20>& powers_of_ten() noexcept
{ {
static const std::array<std::uint64_t, 17> powers = static const std::array<std::uint64_t, 20> powers =
{ {
{ {
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u, 1u,
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u 10u,
100u,
1000u,
10000u,
100000u,
1000000u,
10000000u,
100000000u,
1000000000u,
10000000000u,
100000000000u,
1000000000000u,
10000000000000u,
100000000000000u,
1000000000000000u,
10000000000000000u,
100000000000000000u,
1000000000000000000u,
10000000000000000000u
} }
}; };
return powers; return powers;
@@ -1367,9 +1385,9 @@ JSON_HEDLEY_NON_NULL(1)
JSON_HEDLEY_RETURNS_NON_NULL JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
{ {
JSON_ASSERT(digits >= powers_of_ten_16()[static_cast<std::size_t>(count - 1)] && count <= 15); JSON_ASSERT(digits >= powers_of_ten()[static_cast<std::size_t>(count - 1)] && count <= 15);
const int scale = 16 - count; const int scale = 16 - count;
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten_16()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false}); return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
} }
/// as write_short_decimal(), counting the digits (not 0, less than 10^15) /// as write_short_decimal(), counting the digits (not 0, less than 10^15)
@@ -1380,7 +1398,7 @@ inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noe
JSON_ASSERT(digits != 0 && digits < 1000000000000000u); JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
// floor(log10(2^bits)) + 1 digits, or one less // floor(log10(2^bits)) + 1 digits, or one less
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12; const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
const int count = log2_bound + (digits >= powers_of_ten_16()[static_cast<std::size_t>(log2_bound)] ? 1 : 0); const int count = log2_bound + (digits >= powers_of_ten()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
return write_short_decimal(first, digits, count, exp); return write_short_decimal(first, digits, count, exp);
} }
+304 -11
View File
@@ -14,10 +14,12 @@
#include <cstdint> // uint32_t #include <cstdint> // uint32_t
#include <cstring> // strlen #include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator #include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits
#include <streambuf> // streambuf #include <streambuf> // streambuf
#include <string> // string, char_traits #include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer #include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // pair, declval #include <utility> // pair, declval
#include <vector> // vector
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <cstdio> // FILE * #include <cstdio> // FILE *
@@ -30,6 +32,33 @@
#include <nlohmann/detail/meta/type_traits.hpp> #include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/string_utils.hpp> #include <nlohmann/detail/string_utils.hpp>
#ifndef JSON_NO_IO
// Which unlocked stdio functions file_input_adapter uses (see
// JSON_DISABLE_UNLOCKED_FILE_IO): 1 = POSIX flockfile/getc_unlocked,
// 2 = Microsoft CRT _lock_file/_fgetc_nolock, 0 = none (std::fgetc, which
// locks the file for every character). Only C libraries known to declare
// the functions are listed; the macros are tested after <cstdio>, which
// defines __GLIBC__. MemorySanitizer builds always use std::fgetc: glibc's
// inline getc_unlocked reads the stdio buffer directly, which glibc (not
// instrumented) filled, so MemorySanitizer would report a false positive.
#if defined(__has_feature)
#if __has_feature(memory_sanitizer)
#define JSON_UNLOCKED_FILE_IO_MSAN 1
#endif
#endif
#if JSON_DISABLE_UNLOCKED_FILE_IO || defined(JSON_UNLOCKED_FILE_IO_MSAN)
#define JSON_UNLOCKED_FILE_IO 0
#elif defined(_MSC_VER)
#define JSON_UNLOCKED_FILE_IO 2
#elif defined(__GLIBC__) || defined(__APPLE__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#define JSON_UNLOCKED_FILE_IO 1
#else
#define JSON_UNLOCKED_FILE_IO 0
#endif
#undef JSON_UNLOCKED_FILE_IO_MSAN
#endif // JSON_NO_IO
NLOHMANN_JSON_NAMESPACE_BEGIN NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail namespace detail
{ {
@@ -56,18 +85,35 @@ class file_input_adapter
: m_file(f) : m_file(f)
{ {
JSON_ASSERT(m_file != nullptr); JSON_ASSERT(m_file != nullptr);
lock();
} }
// make class move-only // make class move-only
file_input_adapter(const file_input_adapter&) = delete; file_input_adapter(const file_input_adapter&) = delete;
file_input_adapter(file_input_adapter&&) noexcept = default; file_input_adapter(file_input_adapter&& rhs) noexcept
: m_file(rhs.m_file)
{
// the lock is owned by the thread, not the adapter: hand it over
rhs.m_file = nullptr;
}
file_input_adapter& operator=(const file_input_adapter&) = delete; file_input_adapter& operator=(const file_input_adapter&) = delete;
file_input_adapter& operator=(file_input_adapter&&) = delete; file_input_adapter& operator=(file_input_adapter&&) = delete;
~file_input_adapter() = default; ~file_input_adapter()
{
unlock();
}
std::char_traits<char>::int_type get_character() noexcept std::char_traits<char>::int_type get_character() noexcept
{ {
// the file is locked for the lifetime of the adapter, so the
// per-character lock of std::fgetc can be skipped
#if JSON_UNLOCKED_FILE_IO == 1
return getc_unlocked(m_file);
#elif JSON_UNLOCKED_FILE_IO == 2
return _fgetc_nolock(m_file);
#else
return std::fgetc(m_file); return std::fgetc(m_file);
#endif
} }
// returns the number of characters successfully read // returns the number of characters successfully read
@@ -78,6 +124,32 @@ class file_input_adapter
} }
private: private:
// Lock the file once for the whole parse instead of once per character.
// The lock is recursive, so fread() and the user's own stdio calls on the
// same thread keep working.
void lock() noexcept
{
#if JSON_UNLOCKED_FILE_IO == 1
flockfile(m_file);
#elif JSON_UNLOCKED_FILE_IO == 2
_lock_file(m_file);
#endif
}
void unlock() noexcept
{
// a moved-from adapter has no file and holds no lock
if (m_file == nullptr)
{
return;
}
#if JSON_UNLOCKED_FILE_IO == 1
funlockfile(m_file);
#elif JSON_UNLOCKED_FILE_IO == 2
_unlock_file(m_file);
#endif
}
/// the file pointer to read from /// the file pointer to read from
std::FILE* m_file; std::FILE* m_file;
}; };
@@ -127,15 +199,26 @@ class input_stream_adapter
input_stream_adapter& operator=(input_stream_adapter&) = delete; input_stream_adapter& operator=(input_stream_adapter&) = delete;
input_stream_adapter& operator=(input_stream_adapter&&) = delete; input_stream_adapter& operator=(input_stream_adapter&&) = delete;
#if JSON_PRECISE_STREAM_POSITION
input_stream_adapter(input_stream_adapter&& rhs) noexcept input_stream_adapter(input_stream_adapter&& rhs) noexcept
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead) : is(rhs.is), sb(rhs.sb)
#if JSON_PRECISE_STREAM_POSITION
, lookahead(rhs.lookahead)
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
, token_carry(std::move(rhs.token_carry))
, token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token)
#endif
{ {
rhs.is = nullptr; rhs.is = nullptr;
rhs.sb = nullptr; rhs.sb = nullptr;
#if JSON_PRECISE_STREAM_POSITION
rhs.lookahead = false; rhs.lookahead = false;
#endif
} }
#if JSON_PRECISE_STREAM_POSITION
// Whether the character last returned by get_character() can be given back // Whether the character last returned by get_character() can be given back
// to the input with release_lookahead(). // to the input with release_lookahead().
static constexpr bool supports_lookahead = true; static constexpr bool supports_lookahead = true;
@@ -155,17 +238,41 @@ class input_stream_adapter
sb->sbumpc(); sb->sbumpc();
} }
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sgetc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sgetc(); auto res = sb->sgetc();
// set eof manually, as we don't use the istream interface. // set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof())) if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{ {
// there is nothing to step over next time // there is nothing to step over next time
lookahead = false; lookahead = false;
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit); is->clear(is->rdstate() | std::ios::eofbit);
} }
else else
{ {
lookahead = true; lookahead = true;
#if !JSON_DISABLE_STREAM_BULK_SCAN
if (JSON_HEDLEY_UNLIKELY(refill))
{
// the peeked character is at gptr(), unless the streambuf has
// no get area
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_end(sb));
}
else
{
current_in_get_area = true;
}
#endif
} }
return res; return res;
} }
@@ -179,13 +286,6 @@ class input_stream_adapter
lookahead = false; lookahead = false;
} }
#else #else
input_stream_adapter(input_stream_adapter&& rhs) noexcept
: is(rhs.is), sb(rhs.sb)
{
rhs.is = nullptr;
rhs.sb = nullptr;
}
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
// ensure that std::char_traits<char>::eof() and the character 0xFF do not // ensure that std::char_traits<char>::eof() and the character 0xFF do not
// end up as the same value, e.g., 0xFFFFFFFF. // end up as the same value, e.g., 0xFFFFFFFF.
@@ -194,12 +294,37 @@ class input_stream_adapter
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION. // stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
std::char_traits<char>::int_type get_character() std::char_traits<char>::int_type get_character()
{ {
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc(); auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface. // set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof())) if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{ {
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit); is->clear(is->rdstate() | std::ios::eofbit);
} }
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res; return res;
} }
#endif #endif
@@ -218,7 +343,165 @@ class input_stream_adapter
return res; return res;
} }
#if !JSON_DISABLE_STREAM_BULK_SCAN
// The lexer may scan the characters in the get area of the streambuf
// directly (SWAR fast paths). The get area is only a window into the
// input: it ends wherever the streambuf's buffer ends, and is refilled by
// the next get_character(). The lexer therefore keeps counting the
// position itself and treats the end of the window as "unknown", not as
// the end of the input.
static constexpr bool supports_bulk_scan = true;
static constexpr bool bulk_window = true;
// Pointer to the next unread character; only valid when
// bulk_remaining() > 0. The character before it is the one last returned
// by get_character().
const char* bulk_data() const
{
return current_end();
}
// Number of unread characters in the get area.
std::size_t bulk_remaining() const
{
if (!current_in_get_area)
{
return 0;
}
return static_cast<std::size_t>(streambuf_access::get_end(sb) - current_end());
}
// Consume @a n characters previously inspected via bulk_data(); the last
// of them becomes the character last returned by get_character().
void bulk_skip(std::size_t n)
{
JSON_ASSERT(n <= bulk_remaining());
// gbump() takes an int, but a get area (e.g., of a std::stringbuf) can
// be larger
constexpr auto max_bump = static_cast<std::size_t>((std::numeric_limits<int>::max)());
for (; n > max_bump; n -= max_bump)
{
streambuf_access::bump(sb, static_cast<int>(max_bump));
}
streambuf_access::bump(sb, static_cast<int>(n));
}
// The adapter keeps the current token for the lexer's error messages: the
// part in the get area is only remembered as a pointer, and copied out
// when the get area is about to be refilled.
static constexpr bool supports_token_tracking = true;
// Start a token with the characters read from now on. Until this is
// called, nothing is tracked (the binary readers never call it).
void begin_token() noexcept
{
tracking_token = true;
token_carry.clear();
token_segment = current_end();
}
// Start a new token with the character @a c last returned by
// get_character().
void mark_token_start(std::char_traits<char>::int_type c)
{
tracking_token = true;
token_carry.clear();
if (current_in_get_area)
{
token_segment = current_end() - 1;
}
else
{
token_segment = nullptr;
token_carry.push_back(std::char_traits<char>::to_char_type(c));
}
}
// Copy the characters read since mark_token_start() to @a out, without the
// last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const
{
out = token_carry;
if (token_segment != nullptr)
{
out.insert(out.end(), token_segment, current_end());
}
if (drop_last && !out.empty())
{
out.pop_back();
}
}
#endif
private: private:
#if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers
// accessible; calling them on another streambuf object is then ordinary
// member access through a pointer to member.
struct streambuf_access : std::streambuf
{
static const char* get_ptr(std::streambuf* s)
{
return (s->*(&streambuf_access::gptr))();
}
static const char* get_end(std::streambuf* s)
{
return (s->*(&streambuf_access::egptr))();
}
static const char* get_begin(std::streambuf* s)
{
return (s->*(&streambuf_access::eback))();
}
static void bump(std::streambuf* s, int n)
{
(s->*(&streambuf_access::gbump))(n);
}
};
// one past the character last returned by get_character(), if that is in
// the get area
const char* current_end() const
{
#if JSON_PRECISE_STREAM_POSITION
// the character is peeked at gptr() and not stepped over yet, unless
// it was given back with release_lookahead()
return streambuf_access::get_ptr(sb) + (lookahead ? 1 : 0);
#else
return streambuf_access::get_ptr(sb);
#endif
}
// the get area is about to be replaced: copy the token's part in it
void save_token_segment()
{
if (token_segment != nullptr)
{
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
token_segment = nullptr;
}
}
// character @a c was just read into a new get area (@a in_get_area) or
// returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{
current_in_get_area = in_get_area;
if (!tracking_token)
{
return;
}
if (in_get_area)
{
token_segment = current_end() - 1;
}
else
{
token_carry.push_back(std::char_traits<char>::to_char_type(c));
}
}
#endif
#if JSON_PRECISE_STREAM_POSITION #if JSON_PRECISE_STREAM_POSITION
// Step over the character last returned by get_character(). The character // Step over the character last returned by get_character(). The character
// has already been peeked successfully, so for every streambuf with a get // has already been peeked successfully, so for every streambuf with a get
@@ -240,6 +523,16 @@ class input_stream_adapter
/// whether get_character() peeked a character that is not consumed yet /// whether get_character() peeked a character that is not consumed yet
bool lookahead = false; bool lookahead = false;
#endif #endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
/// the part of the current token that was in earlier get areas
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr
const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false;
#endif
}; };
#endif // JSON_NO_IO #endif // JSON_NO_IO
+278 -34
View File
@@ -166,6 +166,43 @@ constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
return false; return false;
} }
// Detect whether an input adapter keeps the current token itself and can
// hand it out on demand (see input_stream_adapter::supports_token_tracking),
// so the lexer need not copy every scanned character into token_string.
template<typename InputAdapterType>
using detect_supports_token_tracking = decltype(InputAdapterType::supports_token_tracking);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_token_tracking(std::true_type /*detected*/)
{
return InputAdapterType::supports_token_tracking;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_token_tracking(std::false_type /*detected*/)
{
return false;
}
// Detect whether the contiguous block of a bulk-scan adapter is only a window
// into the input (see input_stream_adapter::bulk_window): its end is not the
// end of the input, so a token that touches it must be finished character by
// character.
template<typename InputAdapterType>
using detect_bulk_window = decltype(InputAdapterType::bulk_window);
template<typename InputAdapterType>
constexpr bool input_adapter_bulk_window(std::true_type /*detected*/)
{
return InputAdapterType::bulk_window;
}
template<typename InputAdapterType>
constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
{
return false;
}
/*! /*!
@brief lexical analysis @brief lexical analysis
@@ -184,9 +221,20 @@ class lexer : public lexer_base<BasicJsonType>
/// whether the last read token can be reconstructed from the input adapter /// whether the last read token can be reconstructed from the input adapter
/// on demand (in error paths) instead of being copied on every scanned /// on demand (in error paths) instead of being copied on every scanned
/// character; see input_adapter_supports_seek /// character; see input_adapter_supports_seek
static constexpr bool lazy_token_string = static constexpr bool seekable =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {}); input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
/// whether the adapter keeps the token itself (mark_token_start() /
/// copy_token()); see input_adapter_supports_token_tracking
static constexpr bool adapter_token =
!seekable && input_adapter_supports_token_tracking<InputAdapterType>(is_detected<detect_supports_token_tracking, InputAdapterType> {});
/// whether the last read token is obtained from the input adapter on
/// demand (in error paths) instead of being copied on every scanned
/// character: seekable adapters reconstruct it from the consumed input,
/// token-tracking adapters keep it themselves
static constexpr bool lazy_token_string = seekable || adapter_token;
/// whether the read position is derived from the input adapter on demand /// whether the read position is derived from the input adapter on demand
/// instead of being counted on every scanned character. Seekable adapters /// instead of being counted on every scanned character. Seekable adapters
/// know how much input they consumed and can revisit it, so /// know how much input they consumed and can revisit it, so
@@ -194,7 +242,7 @@ class lexer : public lexer_base<BasicJsonType>
/// and line and column are recomputed from the consumed input only when /// and line and column are recomputed from the consumed input only when
/// get_position() is called (errors). Counting per character kept two /// get_position() is called (errors). Counting per character kept two
/// increments and a newline check in the hot path of every get(). /// increments and a newline check in the hot path of every get().
static constexpr bool lazy_position = lazy_token_string; static constexpr bool lazy_position = seekable;
/// whether a simulated unget can be passed on to the input adapter, which /// whether a simulated unget can be passed on to the input adapter, which
/// then leaves the character in the input; see /// then leaves the character in the input; see
@@ -203,14 +251,17 @@ class lexer : public lexer_base<BasicJsonType>
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {}); input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
/// whether string scanning may bulk-consume runs of ordinary characters /// whether string scanning may bulk-consume runs of ordinary characters
/// directly from a contiguous input buffer (SWAR fast path). This requires /// directly from a contiguous input buffer (SWAR fast path). Without
/// the token to be reconstructible lazily (lazy_token_string), so bypassing /// lazy_token_string / lazy_position, the bulk paths copy the consumed
/// the per-character capture in get() cannot lose error diagnostics. It /// characters into token_string and advance the position counters
/// also implies lazy_position, so the bulk paths need not advance the /// themselves (see bulk_consume).
/// position counters.
static constexpr bool bulk_scan = static constexpr bool bulk_scan =
lazy_token_string input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
/// whether the contiguous block is only a window into the input, so that
/// its end does not end a token; see input_adapter_bulk_window
static constexpr bool bulk_window =
input_adapter_bulk_window<InputAdapterType>(is_detected<detect_bulk_window, InputAdapterType> {});
public: public:
using token_type = typename lexer_base<BasicJsonType>::token_type; using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -219,7 +270,11 @@ class lexer : public lexer_base<BasicJsonType>
: ia(std::move(adapter)) : ia(std::move(adapter))
, ignore_comments(ignore_comments_) , ignore_comments(ignore_comments_)
, discard_number_values(discard_number_values_) , discard_number_values(discard_number_values_)
{} {
// the token string of an error before the first token is everything
// read so far
begin_token(std::integral_constant<bool, adapter_token> {});
}
// deleted because of pointer members // deleted because of pointer members
lexer(const lexer&) = delete; lexer(const lexer&) = delete;
@@ -236,8 +291,8 @@ class lexer : public lexer_base<BasicJsonType>
/// contiguous input: try to decode the 4 hex digits following `\\u` /// contiguous input: try to decode the 4 hex digits following `\\u`
/// directly from the input buffer via hex_codepoint(), instead of 4 calls /// directly from the input buffer via hex_codepoint(), instead of 4 calls
/// to get(). On success, advances the adapter exactly as those 4 get() /// to get(). On success, advances the adapter exactly as those 4 get()
/// calls would (bulk_scan implies lazy_position, so there are no position /// calls would (including the position and token bookkeeping of a
/// counters to advance) and leaves @a current holding the last of /// streaming adapter, see bulk_consume) and leaves @a current holding the last of
/// the 4 digits, just as the last such get() would; the codepoint is /// the 4 digits, just as the last such get() would; the codepoint is
/// written to @a out. Makes no state change and returns false for a /// written to @a out. Makes no state change and returns false for a
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not /// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
@@ -256,7 +311,7 @@ class lexer : public lexer_base<BasicJsonType>
{ {
return false; return false;
} }
ia.bulk_skip(4); bulk_consume(reinterpret_cast<const char*>(raw), 4);
current = char_traits<char_type>::to_int_type(raw[3]); current = char_traits<char_type>::to_int_type(raw[3]);
out = codepoint; out = codepoint;
return true; return true;
@@ -385,7 +440,36 @@ class lexer : public lexer_base<BasicJsonType>
return; return;
} }
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos); token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
ia.bulk_skip(pos); bulk_consume(reinterpret_cast<const char*>(data), pos);
}
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
/// the bookkeeping get() would have done for them; the characters are
/// never newlines
void bulk_consume(const char* data, std::size_t n)
{
bulk_track_position(n, std::integral_constant<bool, lazy_position> {});
bulk_track_token(data, n, std::integral_constant<bool, lazy_token_string> {});
ia.bulk_skip(n);
}
/// seekable adapter: the position is derived from the adapter
void bulk_track_position(std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: count the characters
void bulk_track_position(std::size_t n, std::false_type /*lazy*/) noexcept
{
position.chars_read_total += n;
position.chars_read_current_line += n;
}
/// the token is obtained from the adapter
void bulk_track_token(const char* /*data*/, std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: capture the characters
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
{
token_string.insert(token_string.end(), data, data + n);
} }
/// streaming input: no bulk fast path /// streaming input: no bulk fast path
@@ -1541,9 +1625,12 @@ scan_number_done:
const std::size_t avail = rem + 1; const std::size_t avail = rem + 1;
// validate + classify the number extent (mirrors scan_number()'s grammar) // validate + classify the number extent (mirrors scan_number()'s grammar)
// and accumulate the mantissa digits on the way, so that tokens with up
// to 19 digits need no second pass for their value
std::size_t i = 0; std::size_t i = 0;
std::size_t dot_index = std::string::npos; std::size_t dot_index = std::string::npos;
token_type number_type = token_type::value_unsigned; token_type number_type = token_type::value_unsigned;
std::uint64_t w = 0;
if (data[0] == '-') if (data[0] == '-')
{ {
number_type = token_type::value_integer; number_type = token_type::value_integer;
@@ -1553,22 +1640,21 @@ scan_number_done:
return token_type::uninitialized; return token_type::uninitialized;
} }
} }
const std::size_t integer_begin = i;
if (data[i] == '0') if (data[i] == '0')
{ {
++i; ++i;
} }
else if (data[i] >= '1' && data[i] <= '9') else if (data[i] >= '1' && data[i] <= '9')
{ {
++i; i = accumulate_digits(data, i, avail, w);
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
++i;
}
} }
else else
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
std::size_t digit_count = i - integer_begin;
std::size_t fraction_digits = 0;
if (i < avail && data[i] == '.') if (i < avail && data[i] == '.')
{ {
number_type = token_type::value_float; number_type = token_type::value_float;
@@ -1578,31 +1664,45 @@ scan_number_done:
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
while (i < avail && data[i] >= '0' && data[i] <= '9') const std::size_t fraction_begin = i;
{ i = accumulate_digits(data, i, avail, w);
++i; fraction_digits = i - fraction_begin;
} digit_count += fraction_digits;
} }
// the mantissa ends here, whether or not an exponent part follows // the mantissa ends here, whether or not an exponent part follows
const std::size_t mantissa_end = i; const std::size_t mantissa_end = i;
std::int64_t exponent = 0;
if (i < avail && (data[i] == 'e' || data[i] == 'E')) if (i < avail && (data[i] == 'e' || data[i] == 'E'))
{ {
number_type = token_type::value_float; number_type = token_type::value_float;
++i; ++i;
bool negative_exponent = false;
if (i < avail && (data[i] == '+' || data[i] == '-')) if (i < avail && (data[i] == '+' || data[i] == '-'))
{ {
negative_exponent = data[i] == '-';
++i; ++i;
} }
if (i >= avail || !(data[i] >= '0' && data[i] <= '9')) if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
{ {
return token_type::uninitialized; return token_type::uninitialized;
} }
// saturated far beyond every range, see parse_float_exponent()
constexpr std::int64_t saturation = 100000000000000000; // 10^17
while (i < avail && data[i] >= '0' && data[i] <= '9') while (i < avail && data[i] >= '0' && data[i] <= '9')
{ {
if (exponent < saturation)
{
exponent = (exponent * 10) + (data[i] - '0');
}
++i; ++i;
} }
exponent = negative_exponent ? -exponent : exponent;
} }
const std::size_t len = i; const std::size_t len = i;
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
{
return token_type::uninitialized;
}
// reset() records where this token starts (for diagnostics), so it has // reset() records where this token starts (for diagnostics), so it has
// to run before the input position advances below // to run before the input position advances below
@@ -1617,10 +1717,14 @@ scan_number_done:
#if !JSON_DIAGNOSTIC_POSITIONS #if !JSON_DIAGNOSTIC_POSITIONS
if (number_type != token_type::value_float) if (number_type != token_type::value_float)
{ {
const token_type integer_result = convert_integer(number_type, data, data + len); // up to 18 digits always fit into std::uint64_t and std::int64_t
const token_type integer_result = (digit_count <= 18)
? store_integer(number_type, w)
: convert_integer(number_type, data, data + len);
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized)) if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
{ {
ia.bulk_skip(len - 1); bulk_consume(data + 1, len - 1);
read_number_terminator(std::integral_constant<bool, bulk_window> {});
return integer_result; return integer_result;
} }
// The value does not fit an integer, so this token converts as a // The value does not fit an integer, so this token converts as a
@@ -1630,17 +1734,99 @@ scan_number_done:
} }
#endif #endif
decimal_point_position = dot_index;
bulk_consume(data + 1, len - 1);
read_number_terminator(std::integral_constant<bool, bulk_window> {});
// a float whose digits all fit into w converts from the accumulated
// value; longer ones (and other float formats) take the general path
if (number_type == token_type::value_float && digit_count <= 19)
{
float_significand s;
s.w = w;
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
s.negative = data[0] == '-';
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
{
// the token text is only copied into token_buffer when
// get_string() asks for it; the library's own SAX handlers
// ignore it. A window into a stream may be refilled by the
// next read, so there the text is copied right away.
if (bulk_window)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
}
else
{
pending_number_text = data;
pending_number_length = len;
}
return token_type::value_float;
}
}
// materialize the token exactly as scan_number() would. reset() already // materialize the token exactly as scan_number() would. reset() already
// cleared token_buffer, so append() fills it (assign() is avoided // cleared token_buffer, so append() fills it (assign() is avoided
// because custom string_t types need not provide it) // because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len); token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
decimal_point_position = dot_index;
ia.bulk_skip(len - 1);
return convert_number(number_type, mantissa_end); return convert_number(number_type, mantissa_end);
} }
/// window adapter: a number that reaches the end of the window may continue
/// after it, so scan_number() has to read it character by character
static constexpr bool number_may_continue(bool at_end, std::true_type /*window*/) noexcept
{
return at_end;
}
/// the end of the block is the end of the input, which ends the number
static constexpr bool number_may_continue(bool /*at_end*/, std::false_type /*window*/) noexcept
{
return false;
}
/// window adapter: read the character after a bulk-scanned number and unget
/// it, as scan_number() does. The adapter is a stream that is handed back to
/// the user, who must find the same input consumed either way.
void read_number_terminator(std::true_type /*window*/)
{
get();
unget();
}
/// the input is not handed back, so the terminator can stay unread
void read_number_terminator(std::false_type /*window*/) const noexcept {}
/// binary32/binary64: the value of a float token from its accumulated
/// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{
value_float = decimal_to_float<number_float_t>(s);
return true;
}
/// other formats (e.g. x87 long double): not instantiated for them, as
/// decimal_to_float() only supports binary32/binary64
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
{
return false;
}
/// the value of an integer token with at most 18 digits, accumulated in @a w
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
{
if (number_type == token_type::value_unsigned)
{
value_unsigned = static_cast<number_unsigned_t>(w);
// reject values that do not round-trip into a narrower type
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
}
const std::int64_t x = -static_cast<std::int64_t>(w);
value_integer = static_cast<number_integer_t>(x);
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
}
/// contiguous input: try the number fast path, else the byte-path scanner /// contiguous input: try the number fast path, else the byte-path scanner
token_type scan_number_dispatch(std::true_type /*bulk*/) token_type scan_number_dispatch(std::true_type /*bulk*/)
{ {
@@ -1683,6 +1869,7 @@ scan_number_done:
void reset() noexcept void reset() noexcept
{ {
token_buffer.clear(); token_buffer.clear();
pending_number_text = nullptr;
decimal_point_position = std::string::npos; decimal_point_position = std::string::npos;
#if JSON_DIAGNOSTIC_POSITIONS #if JSON_DIAGNOSTIC_POSITIONS
@@ -1696,11 +1883,30 @@ scan_number_done:
/// seekable adapter: remember where the current token starts so it can be /// seekable adapter: remember where the current token starts so it can be
/// reconstructed from the input on error; current has already been /// reconstructed from the input on error; current has already been
/// consumed, hence the -1 /// consumed, hence the -1
void note_token_start(std::true_type /*lazy*/) noexcept void note_token_start(std::true_type /*lazy*/)
{
note_token_start_lazy(std::integral_constant<bool, adapter_token> {});
}
void note_token_start_lazy(std::false_type /*adapter_token*/) noexcept
{ {
token_string_start = ia.get_consumed_count() - 1; token_string_start = ia.get_consumed_count() - 1;
} }
/// token-tracking adapter: start tracking the token at the first character
void begin_token(std::true_type /*adapter_token*/) noexcept
{
ia.begin_token();
}
void begin_token(std::false_type /*adapter_token*/) const noexcept {}
/// token-tracking adapter: the token starts with the current character
void note_token_start_lazy(std::true_type /*adapter_token*/)
{
ia.mark_token_start(current);
}
/// streaming adapter: start copying the token eagerly, beginning with the /// streaming adapter: start copying the token eagerly, beginning with the
/// already-read first character /// already-read first character
void note_token_start(std::false_type /*lazy*/) noexcept void note_token_start(std::false_type /*lazy*/) noexcept
@@ -1923,10 +2129,30 @@ scan_number_done:
/// return current string value /// return current string value
string_t& get_string() string_t& get_string()
{ {
if (pending_number_text != nullptr)
{
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
pending_number_text = nullptr;
}
// a number token holds '.' regardless of the locale (#4084) // a number token holds '.' regardless of the locale (#4084)
return token_buffer; return token_buffer;
} }
/// the value of a string token (key or string), which never has pending
/// number text, so get_string()'s check is not needed
string_t& get_string_token() noexcept
{
JSON_ASSERT(pending_number_text == nullptr);
return token_buffer;
}
/// the text of a float token for a SAX handler that ignores it (the
/// library's own handlers): empty, without copying the token
const string_t& get_ignored_number_text() const noexcept
{
return token_buffer;
}
///////////////////// /////////////////////
// diagnostics // diagnostics
///////////////////// /////////////////////
@@ -2021,6 +2247,20 @@ scan_number_done:
public: public:
/// seekable adapter: rebuild the last read token from the input on demand /// seekable adapter: rebuild the last read token from the input on demand
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
{
return collect_token_chars_lazy(out, std::integral_constant<bool, adapter_token> {});
}
/// token-tracking adapter: the adapter hands out everything read since
/// mark_token_start(), except a pending unget (see below)
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::true_type /*adapter_token*/) const
{
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
ia.copy_token(out, pending_real_unget);
return out;
}
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::false_type /*adapter_token*/) const
{ {
// a pending unget of a real (non-EOF) character means that character // a pending unget of a real (non-EOF) character means that character
// was consumed from the input but is not part of the token; EOF is // was consumed from the input but is not part of the token; EOF is
@@ -2142,9 +2382,8 @@ scan_number_done:
Pretty-printed JSON indents every line with a run of spaces, which the loop Pretty-printed JSON indents every line with a run of spaces, which the loop
in skip_whitespace() would otherwise read one character at a time. The in skip_whitespace() would otherwise read one character at a time. The
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
other whitespace are left to that loop. Skipping bypasses get(), which is other whitespace are left to that loop. Skipping bypasses get();
fine because bulk_scan implies lazy_position: the position is derived from bulk_consume() does its bookkeeping.
the adapter, not counted per character.
*/ */
void skip_indentation(std::true_type /*bulk*/) void skip_indentation(std::true_type /*bulk*/)
{ {
@@ -2167,7 +2406,7 @@ scan_number_done:
break; break;
} }
} }
ia.bulk_skip(pos); bulk_consume(data, pos);
} }
/// streaming input: no bulk fast path /// streaming input: no bulk fast path
@@ -2309,6 +2548,11 @@ scan_number_done:
/// whether the next get() call should just return current /// whether the next get() call should just return current
bool next_unget = false; bool next_unget = false;
/// the text of the current float token in the input, not yet copied into
/// token_buffer (see get_string()), or nullptr
const char* pending_number_text = nullptr;
std::size_t pending_number_length = 0;
/// the start position of the current token /// the start position of the current token
position_t position {}; position_t position {};
+72 -26
View File
@@ -275,13 +275,77 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u); return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
} }
/// whether [first, last) contains a digit other than '0' /// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
inline bool has_nonzero_digit(const char* first, const char* last) noexcept /// digits (after fast_float)
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
{ {
return std::any_of(first, last, [](char c) noexcept return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
}
/*!
@brief skip the ASCII digits starting at data[i] and accumulate their value
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
around after 19 digits; the caller only uses it for shorter digit runs.
@return the index of the first byte at or after @a i that is not a digit
*/
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
{ {
return c != '0'; while (avail - i >= 8)
}); {
const std::uint64_t v = read_eight_bytes(data + i);
if (!is_eight_digits(v))
{
break;
}
w = (w * 100000000u) + parse_eight_digits(v);
i += 8;
}
while (i < avail && data[i] >= '0' && data[i] <= '9')
{
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
++i;
}
return i;
}
/// whether [first, last) contains a digit other than '0'; a plain loop, as
/// GCC calls std::any_of out of line even for the usual empty range
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
{
for (; first != last; ++first)
{
if (*first != '0')
{
return true;
}
}
return false;
}
/*!
@brief accumulate the validated digits [p, last) into @a w, as long as
@a remaining digits still fit into it
Eight digits are taken at a time while at least eight remain, and @a remaining
is decreased by the number of digits taken.
@return a pointer past the last digit taken
*/
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
{
while (remaining >= 8 && last - p >= 8)
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
return p;
} }
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last), /// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
@@ -327,7 +391,7 @@ truncated is set.
std::string::npos if there is none std::string::npos if there is none
@param[in] mantissa_end index of the 'e'/'E', or the token length @param[in] mantissa_end index of the 'e'/'E', or the token length
*/ */
inline float_significand parse_float_significand(const char* first, const char* last, JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
{ {
float_significand s; float_significand s;
@@ -342,16 +406,7 @@ inline float_significand parse_float_significand(const char* first, const char*
int remaining = 19; // digits that still fit into w int remaining = 19; // digits that still fit into w
if (*p != '0') // the integer part is "0" or [1-9][0-9]* if (*p != '0') // the integer part is "0" or [1-9][0-9]*
{ {
while (remaining >= 8 && integer_last - p >= 8) p = accumulate_significant_digits(p, integer_last, remaining, w);
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != integer_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent = integer_last - p; s.exponent = integer_last - p;
s.truncated = has_nonzero_digit(p, integer_last); s.truncated = has_nonzero_digit(p, integer_last);
} }
@@ -370,16 +425,7 @@ inline float_significand parse_float_significand(const char* first, const char*
s.exponent -= p - zeros; s.exponent -= p - zeros;
} }
const char* const digits = p; const char* const digits = p;
while (remaining >= 8 && mantissa_last - p >= 8) p = accumulate_significant_digits(p, mantissa_last, remaining, w);
{
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
p += 8;
remaining -= 8;
}
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
{
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
}
s.exponent -= p - digits; s.exponent -= p - digits;
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last); s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
} }
+35 -4
View File
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
using parser_callback_t = using parser_callback_t =
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>; std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
/// whether a SAX handler ignores the text of float tokens (the library's own
/// handlers do), so that the parser need not materialize it
template<typename SAX>
struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType>
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
/*! /*!
@brief syntax analysis @brief syntax analysis
@@ -171,6 +185,23 @@ class parser
} }
private: private:
/// the text of the current float token for @a sax (see sax_ignores_number_text)
template<typename SAX>
const string_t& number_float_text(SAX* /*sax*/)
{
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
}
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
{
return m_lexer.get_ignored_number_text();
}
string_t& number_float_text(std::false_type /*ignored*/)
{
return m_lexer.get_string();
}
/*! /*!
@brief run a DOM SAX parser to completion and position the lexer @brief run a DOM SAX parser to completion and position the lexer
@@ -247,7 +278,7 @@ class parser
{ {
return syntax_error(*sax, exception_message(token_type::value_string, "object key")); return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
{ {
return false; return false;
} }
@@ -301,7 +332,7 @@ class parser
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr)); out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
{ {
return false; return false;
} }
@@ -347,7 +378,7 @@ class parser
case token_type::value_string: case token_type::value_string:
{ {
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
{ {
return false; return false;
} }
@@ -456,7 +487,7 @@ class parser
return syntax_error(*sax, exception_message(token_type::value_string, "object key")); return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
} }
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
{ {
return false; return false;
} }
+13 -13
View File
@@ -532,7 +532,7 @@ class json_pointer
{} {}
}; };
std::vector<slot> slots; std::vector<slot> table;
std::size_t used = 0; std::size_t used = 0;
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
@@ -547,19 +547,19 @@ class json_pointer
void grow() void grow()
{ {
std::vector<slot> old(slots.size() * 2); std::vector<slot> old(table.size() * 2);
old.swap(slots); old.swap(table);
const std::size_t mask = slots.size() - 1; const std::size_t mask = table.size() - 1;
for (const auto& s : old) for (const auto& s : old)
{ {
if (s.token != nullptr) if (s.token != nullptr)
{ {
std::size_t i = s.hash & mask; std::size_t i = s.hash & mask;
while (slots[i].token != nullptr) while (table[i].token != nullptr)
{ {
i = (i + 1) & mask; i = (i + 1) & mask;
} }
slots[i] = s; table[i] = s;
} }
} }
} }
@@ -572,7 +572,7 @@ class json_pointer
// start with the empty prefix only // start with the empty prefix only
prefix_tree() prefix_tree()
: slots(16) : table(16)
, parent(1, 0) , parent(1, 0)
, is_array(1, false) , is_array(1, false)
{} {}
@@ -582,20 +582,20 @@ class json_pointer
std::size_t add_child(std::size_t id, const string_t& reference_token) std::size_t add_child(std::size_t id, const string_t& reference_token)
{ {
const std::size_t hash = hash_of(id, reference_token); const std::size_t hash = hash_of(id, reference_token);
const std::size_t mask = slots.size() - 1; const std::size_t mask = table.size() - 1;
std::size_t i = hash & mask; std::size_t i = hash & mask;
while (slots[i].token != nullptr) while (table[i].token != nullptr)
{ {
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token) if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
{ {
return slots[i].id; return table[i].id;
} }
i = (i + 1) & mask; i = (i + 1) & mask;
} }
// new prefix // new prefix
const std::size_t child = parent.size(); const std::size_t child = parent.size();
slots[i] = slot(hash, id, &reference_token, child); table[i] = slot(hash, id, &reference_token, child);
if (reference_token == "0") if (reference_token == "0")
{ {
is_array[id] = true; is_array[id] = true;
@@ -604,7 +604,7 @@ class json_pointer
is_array.push_back(false); is_array.push_back(false);
// keep the load factor below 1/2 // keep the load factor below 1/2
if (++used * 2 > slots.size()) if (++used * 2 > table.size())
{ {
grow(); grow();
} }
@@ -21,6 +21,7 @@
#undef JSON_NO_UNIQUE_ADDRESS #undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION #undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION #undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#undef JSON_UNLOCKED_FILE_IO
#undef JSON_DTOA_SSE2 #undef JSON_DTOA_SSE2
#undef JSON_DTOA_NEON #undef JSON_DTOA_NEON
@@ -47,6 +48,8 @@
#undef JSON_STRICT_BINARY_UTF8 #undef JSON_STRICT_BINARY_UTF8
#undef JSON_DELETE_DEPRECATED_FUNCTIONS #undef JSON_DELETE_DEPRECATED_FUNCTIONS
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS #undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#undef JSON_DISABLE_STREAM_BULK_SCAN
#undef JSON_DISABLE_UNLOCKED_FILE_IO
#endif #endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp> #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
+547 -88
View File
@@ -23,6 +23,7 @@
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64 #include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
#endif #endif
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/binary_reader.hpp> #include <nlohmann/detail/input/binary_reader.hpp>
#include <nlohmann/detail/input/string_scan.hpp> #include <nlohmann/detail/input/string_scan.hpp>
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
@@ -204,24 +205,12 @@ class binary_writer
} }
case value_t::number_integer: case value_t::number_integer:
{
if (j.m_data.m_value.number_integer >= 0)
{
// CBOR does not differentiate between positive signed
// integers and unsigned integers
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
else
{
// a negative integer n is encoded as -1 - n
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
}
break;
}
case value_t::number_unsigned: case value_t::number_unsigned:
{ {
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); // one case for both, so that an array mixing negative
// (number_integer) and non-negative (number_unsigned) values
// does not mispredict the switch
write_cbor_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
break; break;
} }
@@ -256,13 +245,7 @@ class binary_writer
case value_t::array: case value_t::array:
{ {
// step 1: write control byte and the array size write_cbor_array(j, depth);
write_cbor_head(0x80, j.m_data.m_value.array->size());
for (const auto& el : *j.m_data.m_value.array)
{
write_cbor(el, depth + 1);
}
break; break;
} }
@@ -420,54 +403,12 @@ class binary_writer
} }
case value_t::number_integer: case value_t::number_integer:
{
if (j.m_data.m_value.number_integer >= 0)
{
// MessagePack does not differentiate between positive
// signed integers and unsigned integers.
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
else
{
if (j.m_data.m_value.number_integer >= -32)
{
// negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
{
// int 8
oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
{
// int 16
oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
{
// int 32
oa.write_character(to_char_type(0xD2));
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
}
else
{
// int 64
oa.write_character(to_char_type(0xD3));
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
}
}
break;
}
case value_t::number_unsigned: case value_t::number_unsigned:
{ {
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); // one case for both, so that an array mixing negative
// (number_integer) and non-negative (number_unsigned) values
// does not mispredict the switch
write_msgpack_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
break; break;
} }
@@ -485,13 +426,7 @@ class binary_writer
case value_t::array: case value_t::array:
{ {
// step 1: write control byte and the array size write_msgpack_array(j, depth);
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
for (const auto& el : *j.m_data.m_value.array)
{
write_msgpack(el, depth + 1);
}
break; break;
} }
@@ -824,6 +759,142 @@ class binary_writer
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j)); JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
} }
/// how the range of integer type @a NumberType relates to that of @a TargetType
template<typename TargetType, typename NumberType>
struct integer_range
{
/// whether NumberType can hold values above the maximum of TargetType
static constexpr bool may_exceed_max = std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits;
/// whether NumberType can hold values below the minimum of TargetType
static constexpr bool may_fall_below_min = std::numeric_limits<NumberType>::is_signed
&& (!std::numeric_limits<TargetType>::is_signed || may_exceed_max);
};
/*!
@brief whether integer @a n does not exceed the maximum of @a TargetType
The binary formats store integers in at most 64 bits, but number_integer_t
and number_unsigned_t may be wider (e.g., __int128). The range of the
integer is taken from std::numeric_limits, so a number type whose range
fits into @a TargetType is never checked: the function is then constant
true, and the default int64_t/uint64_t types pay nothing.
*/
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_max(const NumberType n) noexcept
{
return integer_fits_max<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_exceed_max>());
}
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_max(const NumberType /*unused*/, std::false_type /*may_exceed*/) noexcept
{
return true;
}
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_max(const NumberType n, std::true_type /*may_exceed*/) noexcept
{
// NumberType has more digits than TargetType, so it can hold its maximum
return n <= static_cast<NumberType>((std::numeric_limits<TargetType>::max)());
}
/*!
@brief whether integer @a n is not below the minimum of @a TargetType;
constant true if NumberType cannot hold such a value
*/
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_min(const NumberType n) noexcept
{
return integer_fits_min<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_fall_below_min>());
}
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_min(const NumberType /*unused*/, std::false_type /*may_fall_below*/) noexcept
{
return true;
}
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits_min(const NumberType n, std::true_type /*may_fall_below*/) noexcept
{
// NumberType is signed and either TargetType is unsigned (minimum 0)
// or NumberType has more digits, so it can hold the minimum
return n >= static_cast<NumberType>((std::numeric_limits<TargetType>::min)());
}
/*!
@brief whether integer @a n lies in the range of @a TargetType
*/
template<typename TargetType, typename NumberType>
static constexpr bool integer_fits(const NumberType n) noexcept
{
return integer_fits_min<TargetType>(n) && integer_fits_max<TargetType>(n);
}
/*!
@brief the decimal digits of the non-negative integer @a n
*/
template<typename NumberType>
static std::string integer_to_string(NumberType n, std::false_type /*is_signed*/)
{
std::string result;
do
{
result.push_back(static_cast<char>('0' + (n % 10)));
n = static_cast<NumberType>(n / 10);
}
while (n != 0);
std::reverse(result.begin(), result.end());
return result;
}
/*!
@brief the decimal digits of the integer @a n, with a leading '-' if it
is negative
*/
template<typename NumberType>
static std::string integer_to_string(NumberType n, std::true_type /*is_signed*/)
{
// the remainder of a negative n is not positive; each digit is
// negated on its own, because negating the minimum of NumberType
// as a whole would overflow
const bool negative = n < 0;
std::string result;
do
{
const auto remainder = n % 10;
result.push_back(static_cast<char>('0' + (remainder < 0 ? -remainder : remainder)));
n = static_cast<NumberType>(n / 10);
}
while (n != 0);
if (negative)
{
result.push_back('-');
}
std::reverse(result.begin(), result.end());
return result;
}
/*!
@brief throws because the integer @a j is too large for @a format_name
@throw out_of_range.407 always
*/
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
{
// neither std::to_string(), which has no overload for integer types
// wider than 64 bits, nor dump(), which would also instantiate the
// serializer for objects and fail for key types it cannot write
using integer_limits = std::numeric_limits<typename BasicJsonType::number_integer_t>;
using unsigned_limits = std::numeric_limits<typename BasicJsonType::number_unsigned_t>;
const std::string number = j.is_number_unsigned()
? integer_to_string(j.m_data.m_value.number_unsigned, std::integral_constant<bool, unsigned_limits::is_signed>())
: integer_to_string(j.m_data.m_value.number_integer, std::integral_constant<bool, integer_limits::is_signed>());
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
static_cast<void>(format_name);
static_cast<void>(range);
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
}
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j) void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
{ {
const auto n = to_msgpack_length(N, j); const auto n = to_msgpack_length(N, j);
@@ -1580,6 +1651,8 @@ class binary_writer
is neither an object nor an array is neither an object nor an array
@throw out_of_range.415 if @a j is binary with a subtype that does not fit @throw out_of_range.415 if @a j is binary with a subtype that does not fit
into a byte, before anything is written into a byte, before anything is written
@throw out_of_range.407 if @a j is an integer that does not fit the 64 bits
of a BSON integer, before anything is written
@throw type_error.316 if @a j is a string that is not valid UTF-8, before @throw type_error.316 if @a j is a string that is not valid UTF-8, before
anything is written anything is written
@throw type_error.321 if @a j is discarded @throw type_error.321 if @a j is discarded
@@ -1598,9 +1671,17 @@ class binary_writer
return 8ul; return 8ul;
case value_t::number_integer: case value_t::number_integer:
return calc_bson_integer_size(j.m_data.m_value.number_integer); if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "BSON", "int64");
}
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
case value_t::number_unsigned: case value_t::number_unsigned:
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{
throw_integer_out_of_range(j, "BSON", "uint64");
}
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
case value_t::string: case value_t::string:
@@ -1639,8 +1720,9 @@ class binary_writer
case value_t::number_float: case value_t::number_float:
return write_bson_double(name, j.m_data.m_value.number_float); return write_bson_double(name, j.m_data.m_value.number_float);
// calc_bson_value_size() checked that integers fit 64 bits
case value_t::number_integer: case value_t::number_integer:
return write_bson_integer(name, j.m_data.m_value.number_integer); return write_bson_integer(name, static_cast<std::int64_t>(j.m_data.m_value.number_integer));
case value_t::number_unsigned: case value_t::number_unsigned:
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned)); return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
@@ -1951,6 +2033,376 @@ class binary_writer
} }
} }
/*!
@brief size class of an unsigned integer from its bit length
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
of leading zero bits instead of found by branches: the sizes of a stream
of numbers are hard to predict, and a mispredicted branch costs more than
writing the number. (A table, as comparisons are vectorized by clang into
slower code.)
*/
static unsigned integer_size_class(const std::uint64_t x) noexcept
{
static constexpr std::array<std::uint8_t, 64> size_class_by_leading_zeros =
{
{
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1
}
};
// x | 1 keeps count_leading_zeros() away from 0
return size_class_by_leading_zeros[static_cast<std::size_t>(count_leading_zeros(x | 1u))];
}
/*!
@brief encode @a first_byte and the low-order bytes of @a value that
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
to @a out
@return the number of characters of the encoding; up to
@ref integer_encoding_space characters at @a out are overwritten
The bytes are written as one byte and one 8-byte word whose bytes after
the value are scratch that the caller drops, so that writing them does not
depend on the size class. For class 0 the word is all scratch; it is
shifted by 60 bits rather than 64, which would be undefined.
*/
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
{
out[0] = to_char_type(first_byte);
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits;
std::memcpy(out + 1, &tail, sizeof(tail));
return ((std::size_t{1} << size_class) >> 1u) + 1;
}
/// characters encode_first_byte_and_tail() may overwrite: the first byte
/// and one 8-byte word
static constexpr std::size_t integer_encoding_space = 9;
/*!
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
that is all ones if it is negative, else zero
Selected through masks rather than branches, as the type and the sign of
the values in an array of mixed signs alternate unpredictably (non-negative
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
63 of the result (a narrower integer is sign-extended); computing it with
masks keeps GCC from branching on the type. A wider integer has to be
compared.
*/
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
{
const bool is_signed = j.m_data.m_type == value_t::number_integer;
const std::uint64_t raw = is_signed
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
return raw;
}
/*!
@brief encode the head of a CBOR integer to @a out without branching on its
size
@return the number of characters of the head
Same output as @ref write_cbor_head. The lengths of strings and containers
are mostly predictable, so their heads keep the branches; the sizes of a
run of integers are not, so an integer's head is assembled without them.
*/
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
{
// an argument of at most 23 is in size class 1 (one byte) and is moved
// to class 0, the first byte, by subtracting 1
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
const unsigned size_class = integer_size_class(argument) - is_immediate;
// the first byte: the argument itself if it is immediate, else
// 0x17 + size_class, selected through a mask
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
}
/*!
@brief write an array value (head and elements) as CBOR
Integer elements, the most common scalar, are written without a call to
@ref write_cbor. The loop is kept out of line: inlined into write_cbor, its
registers would be saved and restored on every call of write_cbor, which
is one call per value nested in an object.
*/
JSON_HEDLEY_NEVER_INLINE
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
{
write_cbor_head(0x80, j.m_data.m_value.array->size());
write_cbor_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
}
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
{
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
{
return encode_cbor_integer(out, el);
}, [this, depth](const BasicJsonType & el)
{
write_cbor(el, depth + 1);
});
}
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
{
for (const auto& el : *j.m_data.m_value.array)
{
write_cbor(el, depth + 1);
}
}
/*!
@brief write the elements of the array @a j, runs of integers with
@a encode_integer and all other values with @a write_value
Runs of integers are encoded straight into space reserved from the sink
for a whole run: the write pointer stays a local, where it would otherwise
be reloaded from the sink for every value, as a store of a character may
alias it.
*/
template<typename EncodeInteger, typename WriteValue>
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
{
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
auto it = j.m_data.m_value.array->begin();
const auto end = j.m_data.m_value.array->end();
while (it != end)
{
if (it->is_number_integer())
{
CharType* const start = oa.reserve(max_run * integer_encoding_space);
CharType* out = start;
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
{
out += encode_integer(out, *it);
}
oa.commit(static_cast<std::size_t>(out - start));
}
else
{
write_value(*it);
++it;
}
}
}
/*!
@brief write a number_integer or number_unsigned value as a CBOR integer
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
Kept out of line, like the other per-value paths, so that write_cbor stays
small (runs of integers in an array are encoded inline instead).
*/
JSON_HEDLEY_NEVER_INLINE
void write_cbor_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
{
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
}
/// write a number_integer or number_unsigned value of a number type wider
/// than 64 bits as a CBOR integer, with range checks on the whole value
void write_cbor_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
{
if (j.m_data.m_type == value_t::number_unsigned)
{
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{
throw_integer_out_of_range(j, "CBOR", "uint64");
}
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return;
}
if (j.m_data.m_value.number_integer >= 0)
{
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
}
// CBOR does not differentiate between positive signed
// integers and unsigned integers
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
else
{
// a negative integer n is encoded as -1 - n
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
}
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
}
}
/// encode a number_integer or number_unsigned value to @a out (see
/// @ref write_cbor_integer) and return the number of characters
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
{
std::uint64_t sign_mask = 0;
const std::uint64_t raw = integer_bits(j, sign_mask);
// ~raw is -1 - n in the 64 bits that are written
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
}
/*!
@brief whether the CBOR and MessagePack integers of BasicJsonType are
written by @ref encode_cbor_integer and @ref encode_msgpack_integer
These pick the format from the low 64 bits; a wider number type is written
with range checks on the whole value instead.
*/
static constexpr bool integers_fit_64_bits() noexcept
{
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
}
/*!
@brief write a number_integer or number_unsigned value as a MessagePack
integer without branching on its sign or size
Same output as @ref write_msgpack: a non-negative integer as positive fixint
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
*/
JSON_HEDLEY_NEVER_INLINE
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
{
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
}
/// encode a number_integer or number_unsigned value to @a out (see
/// @ref write_msgpack_integer) and return the number of characters
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
{
std::uint64_t sign_mask = 0;
const std::uint64_t raw = integer_bits(j, sign_mask);
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
// uint 8/16/32, so x gives the size class of either sign.
const std::uint64_t is_negative = sign_mask & 1u;
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
// A fixint is at most 127, or at least -32 (x at most 63). It is in
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
const unsigned size_class = integer_size_class(x) - is_fixint;
// the first byte: the low byte of the integer for a fixint, else the
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
}
/// write a number_integer or number_unsigned value of a number type wider
/// than 64 bits as a MessagePack integer, with range checks on the whole value
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
{
if (j.m_data.m_type == value_t::number_unsigned)
{
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
{
throw_integer_out_of_range(j, "MessagePack", "uint64");
}
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
return;
}
if (j.m_data.m_value.number_integer >= 0)
{
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
}
// MessagePack does not differentiate between positive
// signed integers and unsigned integers.
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
}
else
{
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
}
if (j.m_data.m_value.number_integer >= -32)
{
// negative fixnum
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
{
// int 8
oa.write_character(to_char_type(0xD0));
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
{
// int 16
oa.write_character(to_char_type(0xD1));
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
}
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
{
// int 32
oa.write_character(to_char_type(0xD2));
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
}
else
{
// int 64
oa.write_character(to_char_type(0xD3));
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
}
}
}
/*!
@brief write an array value (head and elements) as MessagePack
See @ref write_cbor_array.
*/
JSON_HEDLEY_NEVER_INLINE
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
{
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
write_msgpack_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
}
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
{
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
{
return encode_msgpack_integer(out, el);
}, [this, depth](const BasicJsonType & el)
{
write_msgpack(el, depth + 1);
});
}
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
{
for (const auto& el : *j.m_data.m_value.array)
{
write_msgpack(el, depth + 1);
}
}
/*! /*!
@brief write an object key as a CBOR text string @brief write an object key as a CBOR text string
@@ -2147,7 +2599,7 @@ class binary_writer
{ {
return 'L'; return 'L';
} }
if (use_bjdata && std::is_unsigned<NumberType>::value) if (use_bjdata && std::is_unsigned<NumberType>::value && integer_fits_max<std::uint64_t>(n))
{ {
return 'M'; return 'M';
} }
@@ -2272,14 +2724,16 @@ class binary_writer
@brief checks whether a JSON number fits into @a TargetType @brief checks whether a JSON number fits into @a TargetType
@param[in] el a JSON number of either the signed or unsigned integer kind @param[in] el a JSON number of either the signed or unsigned integer kind
@return whether @a el's value can be represented by @a TargetType without @return whether @a el's value can be represented by @a TargetType without
wrapping, regardless of which of the two kinds it is stored as wrapping, regardless of which of the two kinds it is stored as;
false for a value that does not even fit the 64-bit type it is
read as (possible for number types wider than 64 bits)
*/ */
template<typename TargetType> template<typename TargetType>
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el) static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
{ {
return el.is_number_unsigned() return el.is_number_unsigned()
? value_in_range_of<TargetType>(el.template get<std::uint64_t>()) ? integer_fits_max<std::uint64_t>(el.m_data.m_value.number_unsigned) && value_in_range_of<TargetType>(el.template get<std::uint64_t>())
: value_in_range_of<TargetType>(el.template get<std::int64_t>()); : integer_fits<std::int64_t>(el.m_data.m_value.number_integer) && value_in_range_of<TargetType>(el.template get<std::int64_t>());
} }
/*! /*!
@@ -2511,10 +2965,11 @@ class binary_writer
for (const auto& el : dims) for (const auto& el : dims)
{ {
// a dimension is read as an unsigned value below, so anything that // a dimension is read as an unsigned value below, so anything that
// is not a non-negative integer is rejected: a non-integer entry // is not a non-negative integer in the range of std::uint64_t is
// would pun unrelated bytes as the dimension, and a negative one // rejected: a non-integer entry would pun unrelated bytes as the
// would wrap into a nonsensical length // dimension, and a negative or wider one would wrap into a
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0)) // nonsensical length
if (!el.is_number_integer() || !bjdata_ndarray_value_in_range<std::uint64_t>(el))
{ {
return true; return true;
} }
@@ -2646,9 +3101,9 @@ class binary_writer
case value_t::number_unsigned: case value_t::number_unsigned:
{ {
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)())) if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
{ {
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j)); throw_integer_out_of_range(j, "BON8", "int64");
} }
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned)); write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
string_open = false; string_open = false;
@@ -2657,6 +3112,10 @@ class binary_writer
case value_t::number_integer: case value_t::number_integer:
{ {
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
{
throw_integer_out_of_range(j, "BON8", "int64");
}
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer)); write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
string_open = false; string_open = false;
break; break;
@@ -8,7 +8,10 @@
#pragma once #pragma once
#include <algorithm> // max
#include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstring> // memcpy
#include <memory> // shared_ptr, make_shared #include <memory> // shared_ptr, make_shared
#include <string> // basic_string #include <string> // basic_string
#include <type_traits> // conditional, integral_constant, is_same #include <type_traits> // conditional, integral_constant, is_same
@@ -48,32 +51,170 @@ template<typename CharType> struct output_adapter_protocol
template<typename CharType> template<typename CharType>
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
/// the largest number of characters a writer may reserve() from a sink at once;
/// output_vector_sink also appends writes of at least this size with insert()
constexpr std::size_t max_reserve_size = 256;
/// @brief non-virtual output sink writing into a std::vector /// @brief non-virtual output sink writing into a std::vector
/// ///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is /// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
/// passed to binary_writer by value as a template parameter, so /// passed to binary_writer by value as a template parameter, so
/// write_character()/write_characters() are ordinary (inlinable) calls with no /// write_character()/write_characters() are ordinary (inlinable) calls with no
/// vtable lookup and no shared_ptr. It is used for the common /// vtable lookup and no shared_ptr. It is used for the common
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below /// `to_cbor`/`to_msgpack`/... into a std::vector.
/// wraps this same sink to provide the virtual interface. ///
/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8
/// bytes - and appending those with push_back() or insert() cost more than
/// encoding them: insert() is an out-of-line call into a routine that can insert
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
/// its own write position: it grows the vector geometrically ahead of the
/// position and copies each write to it, so a write is a capacity check and a
/// short copy. The destructor shrinks the vector to the bytes actually written,
/// also when a write throws; shrinking a vector never allocates. The sink owns
/// that trim, so it can be moved but not copied, and the vector must not be used
/// while the sink is alive.
template<typename CharType, typename AllocatorType = std::allocator<CharType>> template<typename CharType, typename AllocatorType = std::allocator<CharType>>
class output_vector_sink class output_vector_sink
{ {
public: public:
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
: v(&vec), position(vec.size())
{}
output_vector_sink(const output_vector_sink&) = delete;
output_vector_sink& operator=(const output_vector_sink&) = delete;
output_vector_sink& operator=(output_vector_sink&&) = delete;
output_vector_sink(output_vector_sink&& other) noexcept
: v(other.v), position(other.position)
{
other.v = nullptr;
}
~output_vector_sink()
{
if (v != nullptr)
{
v->resize(position);
}
}
// always inlined: GCC otherwise keeps these out of line in a large
// translation unit, and a call per byte or number costs more than the write
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c)
{
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
{
grow(1);
}
(*v)[position++] = c;
}
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
// pointer with length 0 for empty strings/binary values; memcpy must not
// see it.
JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length)
{
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{
if (length >= max_reserve_size)
{
append(s, length);
return;
}
grow(length);
}
if (length != 0)
{
std::memcpy(v->data() + position, s, length * sizeof(CharType));
position += length;
}
}
/// @brief space for writing up to @a length characters directly
///
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
/// writable characters at the write position; @ref commit then keeps the
/// first ones of them. A writer can store whole words there and keep only
/// the bytes it needs, and keep its write pointer in a register across many
/// values instead of updating the sink after each.
JSON_HEDLEY_ALWAYS_INLINE
CharType* reserve(const std::size_t length)
{
JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{
grow(length);
}
return v->data() + position;
}
/// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE
void commit(const std::size_t length) noexcept
{
position += length;
}
private:
/// make room for at least @a length more characters after the position;
/// kept out of line so that the write functions stay small enough for
/// GCC to inline them into the writer
JSON_HEDLEY_NEVER_INLINE
void grow(const std::size_t length)
{
// use the capacity the vector already has, else at least double it;
// never less than 64 characters, so that a small value does not
// reallocate for every few bytes
const std::size_t needed = position + length;
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
}
/// append @a length (at least max_reserve_size) characters that do not fit
/// the space ahead of the position: insert() copies them once into the
/// grown vector, where grow() would first zero the new space and then copy
/// over it - noticeable for a large string or binary value
JSON_HEDLEY_NEVER_INLINE
void append(const CharType* s, const std::size_t length)
{
v->resize(position);
if (v->capacity() - position < length)
{
// grow geometrically, with room for the writes that follow, so
// that the next grow() need not reallocate right away
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
}
v->insert(v->end(), s, s + length);
position = v->size();
}
std::vector<CharType, AllocatorType>* v;
/// number of characters of *v written so far
std::size_t position;
};
/// output adapter for byte vectors
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
class output_vector_adapter : public output_adapter_protocol<CharType>
{
public:
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
: v(vec) : v(vec)
{} {}
void write_character(CharType c) void write_character(CharType c) override
{ {
v.push_back(c); v.push_back(c);
} }
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null // no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
// pointer with length 0 for empty strings/binary values. Appending an empty // pointer with length 0 for empty strings/binary values; appending an empty
// range is a no-op; the type-erased path tolerates this via the (unattributed) // range is a no-op
// virtual base, and the concrete sink must do the same. void write_characters(const CharType* s, std::size_t length) override
void write_characters(const CharType* s, std::size_t length)
{ {
v.insert(v.end(), s, s + length); v.insert(v.end(), s, s + length);
} }
@@ -82,33 +223,6 @@ class output_vector_sink
std::vector<CharType, AllocatorType>& v; std::vector<CharType, AllocatorType>& v;
}; };
/// output adapter for byte vectors
///
/// The appending itself lives in output_vector_sink; this class only adds the
/// virtual output_adapter_protocol interface on top of it, so both the
/// type-erased and the templated path share one implementation.
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
class output_vector_adapter : public output_adapter_protocol<CharType>
{
public:
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
: sink(vec)
{}
void write_character(CharType c) override
{
sink.write_character(c);
}
void write_characters(const CharType* s, std::size_t length) override
{
sink.write_characters(s, length);
}
private:
output_vector_sink<CharType, AllocatorType> sink;
};
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
/// output adapter for output streams /// output adapter for output streams
template<typename CharType> template<typename CharType>
@@ -188,8 +302,24 @@ class output_adapter_sink
oa->write_characters(s, length); oa->write_characters(s, length);
} }
/// space for writing up to @a length characters (see
/// output_vector_sink::reserve)
CharType* reserve(const std::size_t length) noexcept
{
JSON_ASSERT(length <= max_reserve_size);
static_cast<void>(length);
return reserved.data();
}
/// write the first @a length characters written to the space from reserve()
void commit(const std::size_t length)
{
oa->write_characters(reserved.data(), length);
}
private: private:
output_adapter_t<CharType> oa; output_adapter_t<CharType> oa;
std::array<CharType, max_reserve_size> reserved{};
}; };
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits /// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
@@ -1525,7 +1525,7 @@ class serializer
// without leading zeros // without leading zeros
if (abs_value < 10000000000000000u) if (abs_value < 10000000000000000u)
{ {
const std::uint64_t value = abs_value; const auto value = static_cast<std::uint64_t>(abs_value); // fits: abs_value < 10^16
const std::uint64_t upper = value / 100000000u; const std::uint64_t upper = value / 100000000u;
const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value); const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value);
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0) const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
+6 -4
View File
@@ -14,7 +14,6 @@
#include <cstdint> // uint8_t #include <cstdint> // uint8_t
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <string> // string #include <string> // string
#include <type_traits> // is_signed
#include <nlohmann/detail/macro_scope.hpp> #include <nlohmann/detail/macro_scope.hpp>
#if JSON_HAS_THREE_WAY_COMPARISON #if JSON_HAS_THREE_WAY_COMPARISON
@@ -208,13 +207,16 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
// values of IntegerType lie in [-bound, bound) when signed and in // values of IntegerType lie in [-bound, bound) when signed and in
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a // [0, bound) when unsigned; digits excludes the sign bit, so bound is a
// power of two that the float represents exactly // power of two that the float represents exactly; the signedness comes
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits); // from numeric_limits as well, because std::is_signed is false for class
// types such as 128-bit or multiprecision integers
using limits = std::numeric_limits<IntegerType>;
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
if (f >= bound) if (f >= bound)
{ {
return ordered(-1); return ordered(-1);
} }
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0))) if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
{ {
return ordered(1); return ordered(1);
} }
+11 -9
View File
@@ -422,8 +422,10 @@ class builder
arena_ensure(16); arena_ensure(16);
std::memcpy(m_arena_write, r, 16); std::memcpy(m_arena_write, r, 16);
} }
else 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); arena_ensure(n);
std::memcpy(m_arena_write, r, n); std::memcpy(m_arena_write, r, n);
} }
@@ -682,7 +684,7 @@ obj_next:
return fail(error_code::expected_object_end); return fail(error_code::expected_object_end);
close_object: close_object:
// a large object gets a hash index (objects only, so that closing // a large object gets a hash index for its keys (objects only, so that closing
// an array pays nothing for this) // an array pays nothing for this)
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members)) if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
{ {
@@ -844,7 +846,7 @@ indent_done:
/// append a node: (kind, flags, extra, off) and the second word (len, or /// append a node: (kind, flags, extra, off) and the second word (len, or
/// an integer's value); two stores on little-endian targets /// an integer's value); two stores on little-endian targets
NLOHMANN_VIEW_ALWAYS_INLINE node* emit(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second) NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
{ {
if (NLOHMANN_VIEW_UNLIKELY(out == cap)) if (NLOHMANN_VIEW_UNLIKELY(out == cap))
{ {
@@ -874,9 +876,9 @@ indent_done:
NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k) NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k)
{ {
// (base is read after emit(), which moves the node array when it // (base is read after push_node(), which moves the node array when it
// grows; in one expression the order of the two is unspecified) // grows; in one expression the order of the two is unspecified)
const node* const n = emit(k, 0, 0, static_cast<std::size_t>(p - b), 0); const node* const n = push_node(k, 0, 0, static_cast<std::size_t>(p - b), 0);
const auto idx = static_cast<std::uint32_t>(n - base); const auto idx = static_cast<std::uint32_t>(n - base);
if (depth != 0) if (depth != 0)
{ {
@@ -936,7 +938,7 @@ indent_done:
{ {
return fail(error_code::invalid_literal); return fail(error_code::invalid_literal);
} }
emit(k, flags, 0, static_cast<std::size_t>(p - b), n); push_node(k, flags, 0, static_cast<std::size_t>(p - b), n);
p += n; p += n;
return true; return true;
} }
@@ -1044,7 +1046,7 @@ indent_done:
// of the negative std::int64_t) // of the negative std::int64_t)
second = negative ? 0 - m : m; second = negative ? 0 - m : m;
} }
emit(kind, 0, layout, static_cast<std::size_t>(s - b), second); push_node(kind, 0, layout, static_cast<std::size_t>(s - b), second);
return true; return true;
} }
@@ -1057,7 +1059,7 @@ indent_done:
p = scan_string_run<Value>(p, e); p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"')) if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{ {
emit(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;
return true; return true;
} }
@@ -1067,7 +1069,7 @@ indent_done:
return false; return false;
} }
p = r.p; p = r.p;
emit(value_t::string, node_flags::escaped, 0, r.start, r.len); push_node(value_t::string, node_flags::escaped, 0, r.start, r.len);
return true; return true;
} }
}; };
+9 -6
View File
@@ -201,6 +201,11 @@ class json_side
const BasicJsonType* m_json; const BasicJsonType* m_json;
}; };
inline bool is_number(value_t t) noexcept
{
return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float;
}
/// whether two sides are equal; iterative, so that the nesting depth is /// whether two sides are equal; iterative, so that the nesting depth is
/// limited by memory only /// limited by memory only
template<typename BasicJsonType, typename A, typename B> template<typename BasicJsonType, typename A, typename B>
@@ -225,8 +230,6 @@ bool equal(const A& a0, const B& b0)
{ {
const value_t ta = a.type(); const value_t ta = a.type();
const value_t tb = b.type(); const value_t tb = b.type();
const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float)
&& (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float);
if (ta == value_t::discarded || tb == value_t::discarded) if (ta == value_t::discarded || tb == value_t::discarded)
{ {
// basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) // basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
@@ -237,13 +240,13 @@ bool equal(const A& a0, const B& b0)
} }
else else
{ {
if (!numbers && ta != tb) if (ta != tb && !(is_number(ta) && is_number(tb))) // numbers of different types can be equal
{ {
return false; return false;
} }
if (ta == value_t::string) if (ta == value_t::string)
{ {
if (!(a.string() == b.string())) if (a.string() != b.string())
{ {
return false; return false;
} }
@@ -272,7 +275,7 @@ bool equal(const A& a0, const B& b0)
} }
stack.push_back(std::move(f)); stack.push_back(std::move(f));
} }
else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean else if (a.scalar() != b.scalar()) // numbers (also of different types), null, boolean
{ {
return false; return false;
} }
@@ -289,7 +292,7 @@ bool equal(const A& a0, const B& b0)
const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size(); const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size();
if (f.next == count) if (f.next == count)
{ {
stack.pop_back(); stack.pop_back(); // (f dangles now, and is bound again after continue)
continue; continue;
} }
if (f.object) if (f.object)
+15 -7
View File
@@ -11,7 +11,7 @@
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t #include <cstdint> // uint8_t, uint32_t
#include <cstring> // memcpy #include <cstring> // memcmp, memcpy
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <map> // map #include <map> // map
#include <memory> // unique_ptr #include <memory> // unique_ptr
@@ -46,7 +46,7 @@ struct document_data
std::string owned; ///< owned copy of the input, if any (a std::string, so that its NUL serves as the parser's sentinel) std::string owned; ///< owned copy of the input, if any (a std::string, so that its NUL serves as the parser's sentinel)
std::vector<std::uint8_t> owned_image; ///< a loaded image the document owns (the text and the decoded strings point into it) std::vector<std::uint8_t> owned_image; ///< a loaded image the document owns (the text and the decoded strings point into it)
// hash indexes of large objects (see object_index.hpp) // hash indexes of the keys of large objects (see object_index.hpp)
static constexpr std::uint32_t index_min_members = 128; static constexpr std::uint32_t index_min_members = 128;
struct object_index struct object_index
{ {
@@ -56,14 +56,16 @@ struct document_data
std::vector<object_index> indexes; std::vector<object_index> indexes;
std::vector<std::uint32_t> index_slots; std::vector<std::uint32_t> index_slots;
std::vector<std::uint32_t> large_objects; ///< positions of the objects to index (noted while parsing) std::vector<std::uint32_t> large_objects; ///< positions of the objects to index (noted while parsing)
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage) std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena, unused (indexed by flags & node_flags::storage)
bool discarded = true; bool discarded = true;
/// The storage of edits (editable documents only; see edit_storage.hpp). /// The storage of edits (editable documents only; see edit_storage.hpp).
/// Edits never move or resize the parsed index, so views stay valid: an /// No edit moves or resizes the parsed index, and the nodes of values
/// array/object whose elements change gets node_flags::moved, and its /// written by edits never move either, so a view stays valid whenever it
/// elements then live in a separate sequence (a header node, then the /// was taken, before the first edit or between later ones: an array/object
/// entries), whose entries link to the values. /// whose elements change gets node_flags::moved, and its elements then live
/// in a separate sequence (a header node, then the entries), whose entries
/// link to the values.
struct edit_state struct edit_state
{ {
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init) std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
@@ -183,6 +185,12 @@ struct document_data
return base[n.flags & node_flags::storage] + n.off; return base[n.flags & node_flags::storage] + n.off;
} }
/// whether the string of node k (a key) is the n bytes at key
bool key_equals(const node& k, const char* key, std::size_t n) const noexcept
{
return k.len == n && (n == 0 || std::memcmp(str(k), key, n) == 0);
}
/// the node after n's subtree (containers span `next` nodes, scalars one) /// the node after n's subtree (containers span `next` nodes, scalars one)
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
{ {
+2 -2
View File
@@ -12,7 +12,7 @@
#include <cmath> // isinf, isnan #include <cmath> // isinf, isnan
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t #include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
#include <cstring> // memcmp, memmove #include <cstring> // memmove
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <string> // string, to_string #include <string> // string, to_string
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed #include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
@@ -313,7 +313,7 @@ class editor
bool key_equals(const node& k, string_view_t key) const noexcept bool key_equals(const node& k, string_view_t key) const noexcept
{ {
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0); return m_doc.key_equals(k, key.data(), key.size());
} }
/// remove the members with this key (all, or all but the first) from an object /// remove the members with this key (all, or all but the first) from an object
+20 -5
View File
@@ -50,7 +50,19 @@ inline node* alloc_nodes(document_data& d, std::size_t k)
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k)) if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
{ {
const std::size_t count = (std::max)(k, e.chunk_next); if (k >= e.chunk_next / 2)
{
// a large request (half of the next chunk or more) gets a chunk of
// its own, so that the rest of the current chunk stays available
// for smaller ones
std::unique_ptr<node[]> own(new node[k]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
e.chunks.push_back(std::move(own));
e.bytes += k * sizeof(node);
return e.chunks.back().get();
}
// a smaller request leaves the rest of the current chunk unused
// (fewer than k nodes, and k is less than half of the new chunk)
const std::size_t count = e.chunk_next;
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
e.chunks.push_back(std::move(fresh)); e.chunks.push_back(std::move(fresh));
e.chunk_cur = e.chunks.back().get(); e.chunk_cur = e.chunks.back().get();
@@ -81,7 +93,8 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
} }
/// copy n bytes into the edit arena and return their offset; a new buffer /// copy n bytes into the edit arena and return their offset; a new buffer
/// leaves the old one alive, so that string views into it remain valid /// leaves the old one alive, so that string views into it remain valid; n is
/// 0 for an empty string (written by an edit, or the key of a new member)
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n) inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
{ {
document_data::edit_state& e = edit_state_of(d); document_data::edit_state& e = edit_state_of(d);
@@ -89,7 +102,7 @@ inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
{ {
const std::size_t cap = text_capacity(e.text_cap, e.text_used, n); const std::size_t cap = text_capacity(e.text_cap, e.text_used, n);
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (e.text_used != 0) if (NLOHMANN_VIEW_LIKELY(e.text_used != 0)) // (0 only for the first buffer, and then texts is empty)
{ {
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used); std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
} }
@@ -127,8 +140,10 @@ inline void reserve_moved(document_data& d)
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity()) if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{ {
// both grow before either changes, so that the push_backs cannot throw // both grow before either changes, so that the push_backs cannot throw
e.moved.reserve((2 * e.moved.size()) + 16); // (both conditions are needed: reserve() may allocate more than asked,
e.moved_cap.reserve((2 * e.moved.size()) + 16); // so the two capacities can differ)
e.moved.reserve((e.moved.size() * 2) + 16);
e.moved_cap.reserve((e.moved.size() * 2) + 16);
} }
} }
+23 -34
View File
@@ -8,11 +8,11 @@
#pragma once #pragma once
#include <algorithm> // sort #include <algorithm> // min, sort
#include <array> // array #include <array> // array
#include <cstddef> // size_t #include <cstddef> // offsetof, size_t
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t #include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy #include <cstring> // memcmp, memcpy, memset
#include <limits> // numeric_limits #include <limits> // numeric_limits
#include <string> // string #include <string> // string
#include <vector> // vector #include <vector> // vector
@@ -105,7 +105,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
return r; return r;
}; };
const auto emit = [&](const node * v) const auto copy_node = [&](const node * v)
{ {
node r = *v; node r = *v;
switch (static_cast<value_t>(v->kind)) switch (static_cast<value_t>(v->kind))
@@ -159,10 +159,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
} }
out.push_back(r); out.push_back(r);
}; };
emit(d.nodes); copy_node(d.nodes);
while (!stack.empty()) while (!stack.empty())
{ {
frame& top = stack.back(); const frame top = stack.back();
if (top.cur == top.end) if (top.cur == top.end)
{ {
node& c = out[top.index]; node& c = out[top.index];
@@ -171,20 +171,14 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
stack.pop_back(); stack.pop_back();
continue; continue;
} }
++top.count;
const node* v = nullptr;
if (top.object) if (top.object)
{ {
out.push_back(string_node(*top.cur)); out.push_back(string_node(*top.cur));
v = document_data::deref(top.cur + 1);
top.cur = document_data::after(top.cur + 1);
} }
else const node* const value = top.cur + (top.object ? 1 : 0);
{ stack.back().cur = document_data::after(value);
v = document_data::deref(top.cur); ++stack.back().count;
top.cur = document_data::after(top.cur); copy_node(document_data::deref(value)); // may grow the stack
}
emit(v); // may grow the stack (top is not used afterwards)
} }
} }
@@ -195,17 +189,15 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif #endif
const std::size_t arena_size = d.arena_size; const std::size_t arena_size = d.arena_size;
const node* nodes = d.nodes;
std::size_t count = d.node_count;
std::vector<node> compacted; std::vector<node> compacted;
std::string text_tail; std::string text_tail;
std::string arena_tail; std::string arena_tail;
if (d.edits) if (d.edits)
{ {
compact_nodes(d, arena_size, compacted, text_tail, arena_tail); compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
nodes = compacted.data();
count = compacted.size();
} }
const node* const nodes = d.edits ? compacted.data() : d.nodes;
const std::size_t count = d.edits ? compacted.size() : d.node_count;
const std::size_t text_size = d.size + text_tail.size(); const std::size_t text_size = d.size + text_tail.size();
const std::size_t total_arena = arena_size + arena_tail.size(); const std::size_t total_arena = arena_size + arena_tail.size();
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit)) if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
@@ -225,14 +217,13 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
std::memcpy(o, &h, sizeof(h)); std::memcpy(o, &h, sizeof(h));
o += sizeof(h); o += sizeof(h);
std::memcpy(o, nodes, count * sizeof(node)); std::memcpy(o, nodes, count * sizeof(node));
// the hash indexes are rebuilt by load() // the hash indexes are rebuilt by load(), whose check rejects any other
// extra of an object, and the same document saves to the same bytes
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0) if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0)
{ {
node n = nodes[i]; std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
n.extra = 0;
std::memcpy(o + (i * sizeof(node)), &n, sizeof(node));
} }
} }
o += count * sizeof(node); o += count * sizeof(node);
@@ -271,21 +262,22 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
const unsigned char* p = s; const unsigned char* p = s;
t.negative = p != e && *p == '-'; t.negative = p != e && *p == '-';
p += t.negative ? 1 : 0; p += t.negative ? 1 : 0;
t.int_start = p;
if (p == e) if (p == e)
{ {
return false; return false;
} }
t.int_start = p;
if (*p == '0') if (*p == '0')
{ {
++p; ++p;
} }
else if (*p >= '1' && *p <= '9') else if (*p >= '1' && *p <= '9')
{ {
while (p != e && is_digit(*p)) do
{ {
++p; ++p;
} }
while (p != e && is_digit(*p));
} }
else else
{ {
@@ -318,11 +310,12 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
{ {
return false; return false;
} }
while (p != e && is_digit(*p)) do
{ {
t.exponent = t.exponent < 100000 ? (t.exponent * 10) + (*p - '0') : t.exponent; t.exponent = t.exponent < 100000 ? (t.exponent * 10) + (*p - '0') : t.exponent;
++p; ++p;
} }
while (p != e && is_digit(*p));
t.exponent = exp_negative ? -t.exponent : t.exponent; t.exponent = exp_negative ? -t.exponent : t.exponent;
t.is_float = true; t.is_float = true;
} }
@@ -333,7 +326,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
/// writes "many" instead; the caller accepts both) /// writes "many" instead; the caller accepts both)
inline std::uint16_t float_layout(const number_token& t) inline std::uint16_t float_layout(const number_token& t)
{ {
return static_cast<std::uint16_t>((t.int_digits < 255 ? t.int_digits : 255) | ((t.frac_digits < 255 ? t.frac_digits : 255) << 8u)); return static_cast<std::uint16_t>((std::min)(t.int_digits, std::size_t{255}) | ((std::min)(t.frac_digits, std::size_t{255}) << 8u));
} }
/// whether a float token (well-formed, per scan_number_token) is finite as /// whether a float token (well-formed, per scan_number_token) is finite as
@@ -735,16 +728,12 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
// the hash indexes of large objects, as after parsing // the hash indexes of large objects, as after parsing
for (std::size_t i = 0; i < count; ++i) for (std::size_t i = 0; i < count; ++i)
{ {
node& n = d.nodes[i]; const node& n = d.nodes[i];
if (n.kind == static_cast<std::uint8_t>(value_t::object)) if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members)
{
n.extra = 0;
if (n.len >= document_data::index_min_members)
{ {
d.large_objects.push_back(static_cast<std::uint32_t>(i)); d.large_objects.push_back(static_cast<std::uint32_t>(i));
} }
} }
}
build_object_indexes(d); build_object_indexes(d);
std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building) std::vector<std::uint32_t>().swap(d.large_objects); // (only needed while building)
d.discarded = false; d.discarded = false;
+18 -1
View File
@@ -112,7 +112,7 @@ const node* find_member(const document_data& d, const node* object, const char*
} }
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1)) for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
{ {
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0) if (d.key_equals(*m, key, n))
{ {
return m; return m;
} }
@@ -120,6 +120,23 @@ const node* find_member(const document_data& d, const node* object, const char*
return nullptr; return nullptr;
} }
/// the number of members of an object with this key (duplicates included)
template<bool Editable>
std::size_t count_members(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
{
using nav = navigation<Editable>;
std::size_t result = 0;
const node* const end = nav::end(d, object);
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
{
if (d.key_equals(*m, key, n))
{
++result;
}
}
return result;
}
/// whether an integer type is accepted as an array index by the view's /// whether an integer type is accepted as an array index by the view's
/// operator[] and at(): every integer type but bool and size_t, which has its /// operator[] and at(): every integer type but bool and size_t, which has its
/// own overload /// own overload
+7 -1
View File
@@ -70,5 +70,11 @@
#define NLOHMANN_VIEW_LITTLE_ENDIAN 0 #define NLOHMANN_VIEW_LITTLE_ENDIAN 0
#endif #endif
/// eight checks at fixed offsets 0..7
#define NLOHMANN_VIEW_REPEAT_FIRST_8(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7)
/// eight checks at fixed offsets 8..15
#define NLOHMANN_VIEW_REPEAT_SECOND_8(X) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15)
/// sixteen checks at fixed offsets 0..15 /// sixteen checks at fixed offsets 0..15
#define NLOHMANN_VIEW_REPEAT16(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15) #define NLOHMANN_VIEW_REPEAT16(X) NLOHMANN_VIEW_REPEAT_FIRST_8(X) NLOHMANN_VIEW_REPEAT_SECOND_8(X)
@@ -19,6 +19,8 @@
#undef NLOHMANN_VIEW_THROW #undef NLOHMANN_VIEW_THROW
#undef NLOHMANN_VIEW_LITTLE_ENDIAN #undef NLOHMANN_VIEW_LITTLE_ENDIAN
#undef NLOHMANN_VIEW_REPEAT16 #undef NLOHMANN_VIEW_REPEAT16
#undef NLOHMANN_VIEW_REPEAT_SECOND_8
#undef NLOHMANN_VIEW_REPEAT_FIRST_8
#undef NLOHMANN_VIEW_NEON #undef NLOHMANN_VIEW_NEON
#undef NLOHMANN_VIEW_SSE2 #undef NLOHMANN_VIEW_SSE2
#undef NLOHMANN_VIEW_SSSE3 #undef NLOHMANN_VIEW_SSSE3
+2 -2
View File
@@ -34,7 +34,7 @@ struct node_flags
{ {
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents) static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base) static constexpr std::uint8_t storage = escaped | edited; ///< mask: where a string or number token lives (index into document_data::base)
static constexpr std::uint8_t is_true = 4; ///< boolean value static constexpr std::uint8_t is_true = 4; ///< boolean value
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents) static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
@@ -53,7 +53,7 @@ struct node
{ {
std::uint8_t kind; ///< value_t, or kind_link std::uint8_t kind; ///< value_t, or kind_link
std::uint8_t flags; ///< node_flags std::uint8_t flags; ///< node_flags
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the hash index (1-based, 0 = none); otherwise 0 std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence) std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
+6 -3
View File
@@ -45,10 +45,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
{ {
return false; return false;
} }
while (digit(p)) do
{ {
++p; ++p;
} }
while (digit(p));
dot = std::string::npos; dot = std::string::npos;
if (p != last && *p == '.') if (p != last && *p == '.')
{ {
@@ -57,10 +58,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
{ {
return false; return false;
} }
while (digit(p)) do
{ {
++p; ++p;
} }
while (digit(p));
} }
mantissa_end = static_cast<std::size_t>(p - first); mantissa_end = static_cast<std::size_t>(p - first);
if (p != last && (*p == 'e' || *p == 'E')) if (p != last && (*p == 'e' || *p == 'E'))
@@ -71,10 +73,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
{ {
return false; return false;
} }
while (digit(p)) do
{ {
++p; ++p;
} }
while (digit(p));
} }
return p == last; return p == last;
} }
+24 -29
View File
@@ -10,14 +10,13 @@
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint32_t, uint64_t #include <cstdint> // uint32_t, uint64_t
#include <cstring> // memcmp
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp> #include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/macro_scope.hpp> #include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp> #include <nlohmann/detail/view/node.hpp>
// Hash indexes of large objects, so that a lookup does not compare thousands // Hash indexes of the keys of large objects, so that a lookup does not compare thousands
// of keys (as Boost.JSON switches from a linear search to a hash table for // of keys (as Boost.JSON switches from a linear search to a hash table for
// large objects). An object with document_data::index_min_members members or // large objects). An object with document_data::index_min_members members or
// more gets an open-addressing table after parsing; its node stores the // more gets an open-addressing table after parsing; its node stores the
@@ -42,6 +41,12 @@ namespace view
/// members; and every member costs at most this many steps while building) /// members; and every member costs at most this many steps while building)
constexpr std::size_t index_max_displacement = 64; constexpr std::size_t index_max_displacement = 64;
// a table has at least 2 * len slots (len >= index_min_members), so the probe
// window of index_max_displacement + 1 slots stays far smaller than the table
// and a probe never wraps around onto its own start
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
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
{ {
@@ -77,45 +82,36 @@ inline void build_object_index(document_data& d, node* obj)
} }
const std::size_t start = d.index_slots.size(); const std::size_t start = d.index_slots.size();
d.index_slots.resize(start + cap, 0); d.index_slots.resize(start + cap, 0);
std::uint32_t* const slots = d.index_slots.data() + start; std::uint32_t* const table = d.index_slots.data() + start;
const std::size_t mask = cap - 1; const std::size_t mask = cap - 1;
bool degenerate = false;
for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1)) for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1))
{ {
const char* const key = d.str(*k); const char* const key = d.str(*k);
const std::uint64_t hash = key_hash(key, k->len); // (a cast of the call would be useless where std::uint64_t is std::size_t) std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, k->len)) & mask;
std::size_t i = static_cast<std::size_t>(hash) & mask;
bool duplicate = false; bool duplicate = false;
std::size_t distance = 0; std::size_t distance = 0;
while (slots[i] != 0) while (table[slot] != 0)
{ {
const node* const other = obj + slots[i]; const node* const other = obj + table[slot];
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0)) if (d.key_equals(*other, key, k->len))
{ {
duplicate = true; // keep the first duplicate = true; // keep the first
break; break;
} }
if (++distance > index_max_displacement) if (distance == index_max_displacement)
{ {
degenerate = true; // too many keys share a home region // too many keys share a home region
break; d.index_slots.resize(start); // no table: the object is searched linearly
return;
} }
i = (i + 1) & mask; ++distance;
} slot = (slot + 1) & mask;
if (degenerate)
{
break;
} }
if (!duplicate) if (!duplicate)
{ {
slots[i] = static_cast<std::uint32_t>(k - obj); table[slot] = static_cast<std::uint32_t>(k - obj);
} }
} }
if (degenerate)
{
d.index_slots.resize(start); // no table: the object is searched linearly
return;
}
d.indexes.push_back(document_data::object_index{start, static_cast<std::uint32_t>(mask)}); d.indexes.push_back(document_data::object_index{start, static_cast<std::uint32_t>(mask)});
obj->extra = static_cast<std::uint16_t>(d.indexes.size()); obj->extra = static_cast<std::uint16_t>(d.indexes.size());
} }
@@ -134,22 +130,21 @@ inline void build_object_indexes(document_data& d)
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
{ {
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 slots = d.index_slots.data() + ix.start; const std::uint32_t* const table = d.index_slots.data() + ix.start;
const std::uint64_t hash = key_hash(key, n); // (a cast of the call would be useless where std::uint64_t is std::size_t) std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
std::size_t i = static_cast<std::size_t>(hash) & ix.mask;
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 = slots[i]; const std::uint32_t s = table[slot];
if (s == 0) if (s == 0)
{ {
return nullptr; return nullptr;
} }
const node* const k = obj + s; const node* const k = obj + s;
if (k->len == n && (n == 0 || std::memcmp(d.str(*k), key, n) == 0)) if (d.key_equals(*k, key, n))
{ {
return k; return k;
} }
i = (i + 1) & ix.mask; slot = (slot + 1) & ix.mask;
} }
return nullptr; // (no key sits further from its home slot) return nullptr; // (no key sits further from its home slot)
} }
+12 -12
View File
@@ -47,24 +47,24 @@ enum class index_status
template<typename StringType> template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept index_status array_index(const StringType& s, std::size_t& idx) noexcept
{ {
// as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0') if (s.size() > 1 && s[0] == '0')
{ {
return index_status::leading_zero; return index_status::leading_zero;
} }
// as basic_json: also a single character, but "-" and the empty token are
// left to the conversion below and reported as unresolved
if (!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9'))
{
return index_status::not_number;
}
if (s.empty())
{
return index_status::unresolved;
}
std::uint64_t v = 0; std::uint64_t v = 0;
for (std::size_t i = 0; i < s.size(); ++i) for (const auto c : s)
{ {
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0'; const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10) if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{ {
return index_status::unresolved; // not a number, or beyond unsigned long long return index_status::unresolved; // not a number, or beyond unsigned long long
+3 -30
View File
@@ -83,14 +83,12 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
if (e - p >= 16) if (e - p >= 16)
{ {
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; } #define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
NLOHMANN_VIEW_STEP(0) NLOHMANN_VIEW_STEP(1) NLOHMANN_VIEW_STEP(2) NLOHMANN_VIEW_STEP(3) NLOHMANN_VIEW_REPEAT_FIRST_8(NLOHMANN_VIEW_STEP)
NLOHMANN_VIEW_STEP(4) NLOHMANN_VIEW_STEP(5) NLOHMANN_VIEW_STEP(6) NLOHMANN_VIEW_STEP(7)
#if NLOHMANN_VIEW_VECTOR #if NLOHMANN_VIEW_VECTOR
if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127) if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127)
#endif #endif
{ {
NLOHMANN_VIEW_STEP(8) NLOHMANN_VIEW_STEP(9) NLOHMANN_VIEW_STEP(10) NLOHMANN_VIEW_STEP(11) NLOHMANN_VIEW_REPEAT_SECOND_8(NLOHMANN_VIEW_STEP)
NLOHMANN_VIEW_STEP(12) NLOHMANN_VIEW_STEP(13) NLOHMANN_VIEW_STEP(14) NLOHMANN_VIEW_STEP(15)
p += 8; p += 8;
} }
#undef NLOHMANN_VIEW_STEP #undef NLOHMANN_VIEW_STEP
@@ -177,32 +175,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
/// powers of ten up to 10^19 as integers /// powers of ten up to 10^19 as integers
inline std::uint64_t int_pow10(unsigned k) noexcept inline std::uint64_t int_pow10(unsigned k) noexcept
{ {
static const std::array<std::uint64_t, 20> table = return ::nlohmann::detail::dtoa_impl::powers_of_ten()[k];
{
{
1u,
10u,
100u,
1000u,
10000u,
100000u,
1000000u,
10000000u,
100000000u,
1000000000u,
10000000000u,
100000000000u,
1000000000000u,
10000000000000u,
100000000000000u,
1000000000000000u,
10000000000000000u,
100000000000000000u,
1000000000000000000u,
10000000000000000000u
}
};
return table[k];
} }
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below /// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
+50 -35
View File
@@ -73,6 +73,13 @@ class output_buffer
m_pos += n; m_pos += n;
} }
/// a string literal, without its terminating NUL
template<std::size_t N>
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
put(s, N - 1);
}
void put_repeated(char c, std::size_t n) void put_repeated(char c, std::size_t n)
{ {
reserve(n); reserve(n);
@@ -102,7 +109,7 @@ class output_buffer
/// which a static constexpr member does not have before C++17) /// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept static constexpr std::size_t step() noexcept
{ {
return 65536; return 0x10000;
} }
static StringType& sized(StringType& out, std::size_t estimate) static StringType& sized(StringType& out, std::size_t estimate)
@@ -167,14 +174,17 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
/// A stack that starts in a buffer of the caller (a local array) and moves to /// A stack that starts in a buffer of the caller (a local array) and moves to
/// the heap (a vector of the caller) only when that is full, so that dumps of /// the heap (a vector of the caller) only when that is full, so that dumps of
/// shallow documents need no allocation. The top is a pointer, as in /// shallow documents need no allocation. The top is a pointer, as in
/// std::vector. The address of the stack never escapes (the growth gets the /// std::vector. grow() is static and out of line: it gets the vector and the
/// vector and returns the new storage), so its pointers stay in registers. /// used range and returns the new storage, so the address of the stack never
/// reaches a call. Otherwise the compiler would have to keep the three
/// pointers in memory and load and store them on every push_back() and
/// pop_back(), not just when growing.
template<typename T> template<typename T>
class small_stack class small_stack
{ {
public: public:
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap) : m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap)
{} {}
small_stack(const small_stack&) = delete; small_stack(const small_stack&) = delete;
small_stack(small_stack&&) = delete; small_stack(small_stack&&) = delete;
@@ -188,7 +198,7 @@ class small_stack
{ {
const std::size_t used = size(); const std::size_t used = size();
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin); const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
m_begin = grow(*m_heap, m_begin, used, capacity); m_begin = grow(m_heap, m_begin, used, capacity);
m_top = m_begin + used; m_top = m_begin + used;
m_end = m_begin + capacity; m_end = m_begin + capacity;
} }
@@ -228,9 +238,18 @@ class small_stack
T* m_begin; T* m_begin;
T* m_top; T* m_top;
T* m_end; T* m_end;
std::vector<T>* m_heap; std::vector<T>& m_heap;
}; };
/// a string literal at w, without its terminating NUL (after room())
/// @return a pointer past it
template<std::size_t N>
NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
{
std::memcpy(w, s, N - 1);
return w + N - 1;
}
/// how the view's dump() writes a value /// how the view's dump() writes a value
struct dump_style struct dump_style
{ {
@@ -319,15 +338,15 @@ class view_serializer
m_out.finish(); m_out.finish();
return; return;
} }
frame& f = stack.back(); if (stack.back().pos == stack.back().end)
if (f.pos == f.end)
{ {
const bool object = f.object; const bool object = stack.back().object;
stack.pop_back(); stack.pop_back();
newline(stack.size()); newline(stack.size());
m_out.put(object ? '}' : ']'); m_out.put(object ? '}' : ']');
continue; continue;
} }
frame& f = stack.back();
if (!f.first) if (!f.first)
{ {
m_out.put(','); m_out.put(',');
@@ -339,7 +358,7 @@ class view_serializer
write_string(*f.pos); write_string(*f.pos);
if (m_style.pretty) if (m_style.pretty)
{ {
m_out.put(": ", 2); m_out.put(": ");
} }
else else
{ {
@@ -454,12 +473,6 @@ class view_serializer
} }
w += n; w += n;
}; };
// a literal of n bytes (after room(n))
const auto literal = [&](const char* text, std::size_t n)
{
std::memcpy(w, text, n);
w += n;
};
// a string that is not a plain string of the source (out of line, so // a string that is not a plain string of the source (out of line, so
// that the cursor stays in a register here) // that the cursor stays in a register here)
const auto escaped = [&](const node & n) const auto escaped = [&](const node & n)
@@ -545,11 +558,11 @@ class view_serializer
room(8); room(8);
if ((n->flags & node_flags::is_true) != 0) if ((n->flags & node_flags::is_true) != 0)
{ {
literal("true", 4); w = put_literal(w, "true");
} }
else else
{ {
literal("false", 5); w = put_literal(w, "false");
} }
break; break;
case value_t::object: case value_t::object:
@@ -559,7 +572,7 @@ class view_serializer
room(8); room(8);
if (n->len == 0) if (n->len == 0)
{ {
literal(object ? "{}" : "[]", 2); w = object ? put_literal(w, "{}") : put_literal(w, "[]");
} }
else else
{ {
@@ -579,7 +592,7 @@ class view_serializer
} }
case value_t::null: case value_t::null:
room(8); room(8);
literal("null", 4); w = put_literal(w, "null");
break; break;
case value_t::binary: // LCOV_EXCL_LINE (not in a document) case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE case value_t::discarded: // LCOV_EXCL_LINE
@@ -666,16 +679,16 @@ class view_serializer
switch (static_cast<value_t>(n.kind)) switch (static_cast<value_t>(n.kind))
{ {
case value_t::null: case value_t::null:
m_out.put("null", 4); m_out.put("null");
break; break;
case value_t::boolean: case value_t::boolean:
if ((n.flags & node_flags::is_true) != 0) if ((n.flags & node_flags::is_true) != 0)
{ {
m_out.put("true", 4); m_out.put("true");
} }
else else
{ {
m_out.put("false", 5); m_out.put("false");
} }
break; break;
case value_t::string: case value_t::string:
@@ -759,7 +772,7 @@ class view_serializer
// check also keeps an image that was only checked for bounds, // check also keeps an image that was only checked for bounds,
// whose token may not be made of digits, from the counted // whose token may not be made of digits, from the counted
// overload) // overload)
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten_16(); const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten();
const unsigned count = int_digits + frac_digits; const unsigned count = int_digits + frac_digits;
return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count] return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count]
? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent)) ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent))
@@ -802,12 +815,14 @@ class view_serializer
{ {
if (!std::isfinite(x)) if (!std::isfinite(x))
{ {
m_out.put("null", 4); m_out.put("null");
return; return;
} }
write_float(x, std::integral_constant < bool, static constexpr bool is_ieee_single_or_double
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) = std::numeric_limits<number_float_t>::is_iec559
|| (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024) > {}); && ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
} }
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/) void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
@@ -902,25 +917,25 @@ class view_serializer
switch (codepoint) switch (codepoint)
{ {
case 0x08: case 0x08:
m_out.put("\\b", 2); m_out.put("\\b");
return; return;
case 0x09: case 0x09:
m_out.put("\\t", 2); m_out.put("\\t");
return; return;
case 0x0A: case 0x0A:
m_out.put("\\n", 2); m_out.put("\\n");
return; return;
case 0x0C: case 0x0C:
m_out.put("\\f", 2); m_out.put("\\f");
return; return;
case 0x0D: case 0x0D:
m_out.put("\\r", 2); m_out.put("\\r");
return; return;
case 0x22: case 0x22:
m_out.put("\\\"", 2); m_out.put("\\\"");
return; return;
case 0x5C: case 0x5C:
m_out.put("\\\\", 2); m_out.put("\\\\");
return; return;
default: default:
break; break;
+37 -1
View File
@@ -8,10 +8,11 @@
#pragma once #pragma once
#include <cstddef> // nullptr_t, size_t
#include <cstdint> // int64_t #include <cstdint> // int64_t
#include <map> // map #include <map> // map
#include <string> // basic_string #include <string> // basic_string
#include <type_traits> // enable_if, is_constructible #include <type_traits> // decay, enable_if, is_arithmetic, is_constructible, is_integral, is_same
#include <unordered_map> // unordered_map #include <unordered_map> // unordered_map
#include <vector> // vector #include <vector> // vector
@@ -103,6 +104,41 @@ Map map_value(const View& v)
template<typename Key> template<typename Key>
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {}; struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
/// the arithmetic types arithmetic_value() converts to (bool has its own
/// conversion)
template<typename T>
struct is_non_bool_arithmetic : conjunction<std::is_arithmetic<T>, negation<std::is_same<T, bool>>> {};
/// the default values of value() other than C strings, which have their own
/// overload (returning string_t)
template<typename T>
struct is_non_c_string_default : negation<std::is_same<typename std::decay<T>::type, const char*>> {};
/// the integral types accepted as an array index by the edits of a document
template<typename T>
struct is_non_bool_integral : conjunction<std::is_integral<T>, negation<std::is_same<T, bool>>> {};
/// whether View::get<T>() converts to T without materialize(); get_to()
/// assigns these, and passes the others to from_json
template<typename T, typename BasicJsonType, typename View>
struct is_direct_value : std::integral_constant < bool,
std::is_arithmetic<T>::value || std::is_same<T, std::nullptr_t>::value
|| std::is_same<T, typename View::string_view_t>::value
|| std::is_same<T, BasicJsonType>::value || std::is_same<T, View>::value >
{};
template<typename Traits, typename Alloc, typename BasicJsonType, typename View>
struct is_direct_value<std::basic_string<char, Traits, Alloc>, BasicJsonType, View> : std::true_type {};
template<typename U, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::vector<U, A>, BasicJsonType, View> : std::true_type {};
template<typename K, typename V, typename C, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::map<K, V, C, A>, BasicJsonType, View> : is_string_key<K> {};
template<typename K, typename V, typename H, typename E, typename A, typename BasicJsonType, typename View>
struct is_direct_value<std::unordered_map<K, V, H, E, A>, BasicJsonType, View> : is_string_key<K> {};
} // namespace view } // namespace view
} // namespace detail } // namespace detail
NLOHMANN_JSON_NAMESPACE_END NLOHMANN_JSON_NAMESPACE_END
+41 -22
View File
@@ -31,12 +31,12 @@
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep #include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
#include <cstdint> // uint8_t, uint32_t #include <cstdint> // uint8_t, uint32_t
#include <cstring> // memcpy, strlen #include <cstring> // memcpy, strlen
#include <istream> // istream #include <istream> // istream // IWYU pragma: keep
#include <iterator> // distance, input_iterator_tag, iterator_traits #include <iterator> // distance, input_iterator_tag, iterator_traits
#include <map> // map #include <map> // map
#include <memory> // unique_ptr #include <memory> // unique_ptr
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
#include <ostream> // ostream #include <ostream> // ostream // IWYU pragma: keep
#endif #endif
#include <string> // string #include <string> // string
#include <tuple> // tuple_element, tuple_size // IWYU pragma: keep #include <tuple> // tuple_element, tuple_size // IWYU pragma: keep
@@ -265,7 +265,7 @@ class basic_json_view
{ {
if (is_discarded()) if (is_discarded())
{ {
return basic_json_view(); return {};
} }
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name()); detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
} }
@@ -291,7 +291,7 @@ class basic_json_view
{ {
if (is_discarded()) if (is_discarded())
{ {
return basic_json_view(); return {};
} }
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());
} }
@@ -314,7 +314,7 @@ class basic_json_view
{ {
if (NLOHMANN_VIEW_UNLIKELY(is_discarded())) if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
{ {
return basic_json_view(); return {};
} }
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked); return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
} }
@@ -379,7 +379,7 @@ class basic_json_view
/// the member with this key converted to T, or the default value if there /// the member with this key converted to T, or the default value if there
/// is no such member (the first one, should the key occur more than /// is no such member (the first one, should the key occur more than
/// once). Throws type_error.306 if this is not an object. /// once). Throws type_error.306 if this is not an object.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 > template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
T value(string_view_t key, const T& default_value) const T value(string_view_t key, const T& default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_object())) if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
@@ -398,7 +398,7 @@ class basic_json_view
/// 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
/// value if the pointer cannot be resolved. Throws type_error.306 if this /// value if the pointer cannot be resolved. Throws type_error.306 if this
/// is neither an object nor an array. /// is neither an object nor an array.
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 > template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
T value(const json_pointer& ptr, const T& default_value) const T value(const json_pointer& ptr, const T& default_value) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(!is_structured())) if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
@@ -487,11 +487,11 @@ class basic_json_view
return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded(); return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
} }
/// 1 if this is an object with a member with this key, else 0 (duplicate /// the number of members with this key (more than 1 if the key occurs
/// keys count once) /// more than once); 0 if this is not an object
size_type count(string_view_t key) const size_type count(string_view_t key) const
{ {
return contains(key) ? 1 : 0; return is_object() ? detail::view::count_members<Editable>(*m_doc, m_node, key.data(), key.size()) : 0;
} }
size_type count(const char* key) const size_type count(const char* key) const
@@ -558,10 +558,12 @@ class basic_json_view
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {}); return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
} }
/// as BasicJsonType::get_to(): the types get<T>() converts directly are
/// assigned, all others are passed to from_json with v as it is
template<typename T> template<typename T>
T& get_to(T& v) const T& get_to(T& v) const
{ {
v = get<T>(); get_to_impl(v, detail::view::is_direct_value<T, BasicJsonType, basic_json_view> {});
return v; return v;
} }
@@ -748,8 +750,10 @@ class basic_json_view
{ {
if (editable() && m_doc->edits != nullptr) if (editable() && m_doc->edits != nullptr)
{ {
// positions of moved and new values are not source offsets // positions of moved and new values are not source offsets; the root
return m_node == m_doc->nodes ? m_doc->size + m_doc->edits->text_used : 64; // (the first node) spans the source text and every edit
const bool is_root = m_node == m_doc->nodes;
return is_root ? m_doc->size + m_doc->edits->text_used : 64;
} }
const node* const end = m_doc->nodes + m_doc->node_count; const node* const end = m_doc->nodes + m_doc->node_count;
if ((m_node->flags & detail::view::node_flags::storage) != 0) if ((m_node->flags & detail::view::node_flags::storage) != 0)
@@ -796,7 +800,7 @@ class basic_json_view
return (m_node->flags & detail::view::node_flags::is_true) != 0; return (m_node->flags & detail::view::node_flags::is_true) != 0;
} }
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 > template<typename T, typename std::enable_if<detail::view::is_non_bool_arithmetic<T>::value, int>::type = 0>
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
{ {
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr)) if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
@@ -862,6 +866,21 @@ class basic_json_view
return materialize().template get<T>(); return materialize().template get<T>();
} }
// --- get_to() dispatch ---
template<typename T>
void get_to_impl(T& v, std::true_type /*direct*/) const
{
v = get<T>();
}
/// from_json may keep parts of v (members a JSON object lacks, say)
template<typename T>
void get_to_impl(T& v, std::false_type /*direct*/) const
{
materialize().get_to(v);
}
const document_data* m_doc = nullptr; const document_data* m_doc = nullptr;
const node* m_node = nullptr; const node* m_node = nullptr;
}; };
@@ -1021,7 +1040,7 @@ class basic_json_document
return m_data ? m_data->node_count : 0; return m_data ? m_data->node_count : 0;
} }
/// bytes held by the document (index, decoded strings, owned text or image) /// bytes held by the document (index, decoded strings, and owned text or image)
std::size_t memory_usage() const noexcept std::size_t memory_usage() const noexcept
{ {
if (!m_data) if (!m_data)
@@ -1169,7 +1188,7 @@ class basic_json_document
} }
/// assign an existing array element; returns a view of it /// assign an existing array element; returns a view of it
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 > template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
view_type set(view_type array, I idx, V && value) view_type set(view_type array, I idx, V && value)
{ {
return editor().set(array, index(idx), std::forward<V>(value)); return editor().set(array, index(idx), std::forward<V>(value));
@@ -1191,14 +1210,14 @@ class basic_json_document
const auto& token = ptr.back(); const auto& token = ptr.back();
if (parent.is_null()) if (parent.is_null())
{ {
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
{
return c >= '0' && c <= '9';
});
if (token == "-") if (token == "-")
{ {
return push_back(parent, std::forward<V>(value)); return push_back(parent, std::forward<V>(value));
} }
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
{
return c >= '0' && c <= '9';
});
if (digits) if (digits)
{ {
// (an invalid index is an error before the parent changes) // (an invalid index is an error before the parent changes)
@@ -1236,7 +1255,7 @@ class basic_json_document
/// insert into an array before position idx (idx <= size()); returns a /// insert into an array before position idx (idx <= size()); returns a
/// view of the new element /// view of the new element
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 > template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
view_type insert(view_type array, I idx, V && value) view_type insert(view_type array, I idx, V && value)
{ {
return editor().insert(array, index(idx), std::forward<V>(value)); return editor().insert(array, index(idx), std::forward<V>(value));
@@ -1249,7 +1268,7 @@ class basic_json_document
} }
/// remove an array element /// remove an array element
template < typename I, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 > template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
void erase(view_type array, I idx) void erase(view_type array, I idx)
{ {
editor().erase(array, index(idx)); editor().erase(array, index(idx));
+568320
View File
File diff suppressed because it is too large. Load diff
+1490 -227
View File
File diff suppressed because it is too large. Load diff
+40 -4
View File
@@ -71,6 +71,14 @@
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0 #define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif #endif
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
#define JSON_DISABLE_STREAM_BULK_SCAN 0
#endif
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
#endif
#if JSON_DIAGNOSTICS #if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else #else
@@ -119,14 +127,38 @@
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS #define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif #endif
#if JSON_DISABLE_STREAM_BULK_SCAN
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
#else
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
#endif
#if JSON_DISABLE_UNLOCKED_FILE_IO
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
#else
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
#endif
#ifdef JSON_VIEW_NO_SIMD
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
#else
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD
#endif
#ifdef JSON_VIEW_USE_SSSE3
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3
#else
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif #endif
// Construct the namespace ABI tags component // Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h #define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \ #define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
#define NLOHMANN_JSON_ABI_TAGS \ #define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -137,7 +169,11 @@
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \ NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \ NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \ NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS) NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
// Construct the namespace version component // Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+282 -199
View File
File diff suppressed because it is too large. Load diff
+31
View File
@@ -285,6 +285,10 @@ if (JSON_TestSimdutf)
endif() endif()
endif() endif()
# test-file_input checks from a second thread that parsing from a FILE* releases the file's lock
find_package(Threads REQUIRED)
json_test_set_test_options("test-file_input;test-file_input_unlocked_io_disabled" LINK_LIBRARIES Threads::Threads)
# *DO* use json_test_set_test_options() above this line # *DO* use json_test_set_test_options() above this line
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}") json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
@@ -369,6 +373,33 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
) )
# test reading from a std::istream again without scanning the get area in bulk
json_test_set_test_options(test-stream-bulk-scan_disabled
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
)
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
NAME test-stream-bulk-scan_disabled
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# test reading from a std::istream again with JSON_PRECISE_STREAM_POSITION
json_test_set_test_options(test-stream-bulk-scan_precise
COMPILE_DEFINITIONS JSON_PRECISE_STREAM_POSITION=1
)
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
NAME test-stream-bulk-scan_precise
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# test reading from a FILE* again with the locking stdio functions
json_test_set_test_options(test-file_input_unlocked_io_disabled
COMPILE_DEFINITIONS JSON_DISABLE_UNLOCKED_FILE_IO=1
)
json_test_add_test_for(src/unit-file_input.cpp
NAME test-file_input_unlocked_io_disabled
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
# the json_view parser again with the portable string scanning instead of # the json_view parser again with the portable string scanning instead of
# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3) # NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3)
json_test_set_test_options(test-json_view_builder_portable json_test_set_test_options(test-json_view_builder_portable
+5 -1
View File
@@ -21,7 +21,11 @@ foreach(test default noversion)
target_compile_definitions(abi_config_${test}_all_tags PRIVATE target_compile_definitions(abi_config_${test}_all_tags PRIVATE
JSON_DIAGNOSTICS=1 JSON_DIAGNOSTICS=1
JSON_DIAGNOSTIC_POSITIONS=1 JSON_DIAGNOSTIC_POSITIONS=1
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1) JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
JSON_DISABLE_STREAM_BULK_SCAN=1
JSON_DISABLE_UNLOCKED_FILE_IO=1
JSON_VIEW_NO_SIMD
JSON_VIEW_USE_SSSE3)
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main) target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
add_test( add_test(
NAME test-abi_config_${test}_all_tags NAME test-abi_config_${test}_all_tags
+16
View File
@@ -52,6 +52,22 @@ TEST_CASE("default namespace")
expected += "_ekmo"; expected += "_ekmo";
#endif #endif
#if JSON_DISABLE_STREAM_BULK_SCAN
expected += "_dsbs";
#endif
#if JSON_DISABLE_UNLOCKED_FILE_IO
expected += "_dufio";
#endif
#ifdef JSON_VIEW_NO_SIMD
expected += "_vns";
#endif
#ifdef JSON_VIEW_USE_SSSE3
expected += "_vssse3";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR); expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR); expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json"; expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
+16
View File
@@ -53,6 +53,22 @@ TEST_CASE("default namespace without version component")
expected += "_ekmo"; expected += "_ekmo";
#endif #endif
#if JSON_DISABLE_STREAM_BULK_SCAN
expected += "_dsbs";
#endif
#if JSON_DISABLE_UNLOCKED_FILE_IO
expected += "_dufio";
#endif
#ifdef JSON_VIEW_NO_SIMD
expected += "_vns";
#endif
#ifdef JSON_VIEW_USE_SSSE3
expected += "_vssse3";
#endif
expected += "::basic_json"; expected += "::basic_json";
// fallback for Clang // fallback for Clang
+9
View File
@@ -22,6 +22,15 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` | | `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value | | `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` | | `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
| `Copy`, `CopyAssign`, `CopyRing` | copy a parsed document and destroy a copy, both timed: a temporary copy, an assignment over an earlier copy, and replacing the oldest of eight live copies |
| `CopyOnly`, `DestroyOnly` | only the copy or only the destruction, the other half untimed; a fast destructor can leave work to the next allocations, so these tell the halves apart but do not compare versions |
| `Equal`, `Iterate` | compare a parsed document with a copy, or visit every value with range-based `for` loops |
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved, and destroy it again |
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`, The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...). `citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
+643 -1
View File
@@ -8,11 +8,16 @@
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <fstream>
#include <array> #include <array>
#include <cstdint>
#include <cstdio>
#include <deque> #include <deque>
#include <fstream>
#include <map>
#include <numeric> #include <numeric>
#include <set> #include <set>
#include <sstream>
#include <string>
#include <vector> #include <vector>
#include <test_data.hpp> #include <test_data.hpp>
@@ -51,6 +56,65 @@ BENCHMARK_CAPTURE(ParseFile, signed_ints, TEST_DATA_DIRECTORY "/regression
BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json"); BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json"); BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
//////////////////////////////////////////////////////////////////////////////
// parse JSON from a FILE*
//////////////////////////////////////////////////////////////////////////////
static void ParseFILE(benchmark::State& state, const char* filename)
{
while (state.KeepRunning())
{
state.PauseTiming();
std::FILE* f = std::fopen(filename, "rb");
auto* j = new json();
state.ResumeTiming();
*j = json::parse(f);
state.PauseTiming();
std::fclose(f);
delete j;
state.ResumeTiming();
}
std::ifstream file(filename, std::ios::binary | std::ios::ate);
state.SetBytesProcessed(state.iterations() * file.tellg());
}
BENCHMARK_CAPTURE(ParseFILE, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(ParseFILE, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(ParseFILE, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// read many small values from one stream (NDJSON)
//////////////////////////////////////////////////////////////////////////////
static void ParseStreamValues(benchmark::State& state, bool numbers)
{
std::string input;
for (int i = 0; i < 10000; ++i)
{
input += numbers ? std::to_string(i * 7919) + " "
: R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","active":true,"score":)" + std::to_string(i * 0.25) + "}\n";
}
for (auto _ : state)
{
std::istringstream is(input);
json j;
std::size_t count = 0;
while (!(is >> std::ws).eof())
{
is >> j;
++count;
}
benchmark::DoNotOptimize(count);
}
state.SetBytesProcessed(static_cast<int64_t>(state.iterations() * input.size()));
}
BENCHMARK_CAPTURE(ParseStreamValues, ndjson, false);
BENCHMARK_CAPTURE(ParseStreamValues, numbers, true);
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
// parse JSON from string // parse JSON from string
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@@ -649,6 +713,584 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000); BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
//////////////////////////////////////////////////////////////////////////////
// SAX-parse JSON from string
//
// sax_parse() with a handler that does next to nothing runs the lexer and
// parser without building values, so the gap to ParseString above is the cost
// of building the DOM.
//////////////////////////////////////////////////////////////////////////////
static std::string read_file(const char* filename)
{
std::ifstream f(filename);
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
}
static json load_json(const char* filename)
{
return json::parse(read_file(filename));
}
/// SAX handler that only counts the events, so that the handler costs next to nothing
class counting_sax : public nlohmann::json_sax<json>
{
public:
std::size_t events = 0;
bool null() override
{
return count();
}
bool boolean(bool /*val*/) override
{
return count();
}
bool number_integer(number_integer_t /*val*/) override
{
return count();
}
bool number_unsigned(number_unsigned_t /*val*/) override
{
return count();
}
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
{
return count();
}
bool string(string_t& /*val*/) override
{
return count();
}
bool binary(binary_t& /*val*/) override
{
return count();
}
bool start_object(std::size_t /*elements*/) override
{
return count();
}
bool key(string_t& /*val*/) override
{
return count();
}
bool end_object() override
{
return count();
}
bool start_array(std::size_t /*elements*/) override
{
return count();
}
bool end_array() override
{
return count();
}
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
{
return false;
}
private:
bool count()
{
++events;
return true;
}
};
static void SaxParse(benchmark::State& state, const char* filename)
{
const std::string str = read_file(filename);
for (auto _ : state)
{
counting_sax sax;
bool result = json::sax_parse(str, &sax);
benchmark::DoNotOptimize(result);
benchmark::DoNotOptimize(sax.events);
}
state.SetBytesProcessed(state.iterations() * str.size());
}
BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// work with values: copy, destroy, compare, iterate
//
// The parsed test files stand in for a document an application keeps around.
// Each benchmark times one whole-document operation; setup and cleanup run
// outside the timed section.
//
// A copy is always destroyed again, and how a document is freed shapes the
// heap the next copy allocates from: a destructor that frees quickly can leave
// work to the allocator's next calls. Copy, CopyAssign and CopyRing therefore
// time the copy together with the destruction of a copy; CopyOnly and
// DestroyOnly time one half each and are meant for telling the halves apart,
// not for comparing versions.
//////////////////////////////////////////////////////////////////////////////
/// copy a document and destroy the copy again, as a temporary copy is used
static void Copy(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
json copy = j;
benchmark::DoNotOptimize(copy);
}
}
BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// copy-assign a document over an earlier copy of it, which is freed in the process
static void CopyAssign(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
json target = j;
for (auto _ : state)
{
target = j;
benchmark::DoNotOptimize(target);
}
}
BENCHMARK_CAPTURE(CopyAssign, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyAssign, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyAssign, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// keep eight copies alive and replace the oldest one, as a cache or an undo
/// history does, so that the copy is not made from memory just freed by an
/// identical document
static void CopyRing(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
std::vector<json> ring(8, j);
std::size_t oldest = 0;
for (auto _ : state)
{
ring[oldest] = j;
benchmark::DoNotOptimize(ring[oldest]);
oldest = (oldest + 1) % ring.size();
}
}
BENCHMARK_CAPTURE(CopyRing, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyRing, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyRing, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// only the copy; the copy is destroyed with the timer paused (diagnostic)
static void CopyOnly(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto* copy = new json(j);
state.PauseTiming();
delete copy;
state.ResumeTiming();
}
}
BENCHMARK_CAPTURE(CopyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(CopyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(CopyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// only the destruction; the copy is made with the timer paused (diagnostic)
static void DestroyOnly(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
state.PauseTiming();
auto* copy = new json(j);
state.ResumeTiming();
delete copy;
}
}
BENCHMARK_CAPTURE(DestroyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(DestroyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(DestroyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Equal(benchmark::State& state, const char* filename)
{
// two separate values, so that the comparison cannot stop at identical addresses
const json j = load_json(filename);
const json copy = j;
for (auto _ : state)
{
bool result = (j == copy);
benchmark::DoNotOptimize(result);
}
}
BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
/// visit every value with range-based for loops, as client code walks a document
static std::size_t count_values(const json& j)
{
std::size_t result = 1;
if (j.is_structured())
{
for (const auto& element : j)
{
result += count_values(element);
}
}
return result;
}
static void Iterate(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
std::size_t result = count_values(j);
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(count_values(j)));
}
BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// flatten and unflatten
//////////////////////////////////////////////////////////////////////////////
static void Flatten(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
json flat = j.flatten();
benchmark::DoNotOptimize(flat);
}
}
BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
// ordered_json objects look keys up linearly, which flatten() used to do for
// every value; canada (330,000 values in one flat object) is left out, as it
// would take minutes with such versions
static void FlattenOrdered(benchmark::State& state, const char* filename)
{
const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename));
for (auto _ : state)
{
nlohmann::ordered_json flat = j.flatten();
benchmark::DoNotOptimize(flat);
}
}
BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Unflatten(benchmark::State& state, const char* filename)
{
const json flat = load_json(filename).flatten();
for (auto _ : state)
{
json j = flat.unflatten();
benchmark::DoNotOptimize(j);
}
}
BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// diff and patch
//
// The target is the document with every eighth scalar changed, so the patch
// has one "replace" operation per changed value and the structure stays the
// same.
//////////////////////////////////////////////////////////////////////////////
static void change_every_eighth_scalar(json& j, std::size_t& counter)
{
if (j.is_structured())
{
for (auto& element : j)
{
change_every_eighth_scalar(element, counter);
}
return;
}
if (++counter % 8 != 0)
{
return;
}
switch (j.type())
{
case json::value_t::number_integer:
j = j.get<json::number_integer_t>() + 1;
break;
case json::value_t::number_unsigned:
j = j.get<json::number_unsigned_t>() + 1;
break;
case json::value_t::number_float:
j = j.get<json::number_float_t>() + 0.5;
break;
case json::value_t::string:
j = j.get<std::string>() + "!";
break;
case json::value_t::boolean:
j = !j.get<bool>();
break;
case json::value_t::null:
case json::value_t::object:
case json::value_t::array:
case json::value_t::binary:
case json::value_t::discarded:
default:
j = 0;
break;
}
}
static json changed_copy(const json& j)
{
json result = j;
std::size_t counter = 0;
change_every_eighth_scalar(result, counter);
return result;
}
static void Diff(benchmark::State& state, const char* filename)
{
const json source = load_json(filename);
const json target = changed_copy(source);
for (auto _ : state)
{
json patch = json::diff(source, target);
benchmark::DoNotOptimize(patch);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(json::diff(source, target).size()));
}
BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void Patch(benchmark::State& state, const char* filename)
{
const json source = load_json(filename);
const json patch = json::diff(source, changed_copy(source));
for (auto _ : state)
{
json result = source.patch(patch);
benchmark::DoNotOptimize(result);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(patch.size()));
}
BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// JSON Pointer
//
// The pointers are those of every scalar in the document, i.e., the keys of
// its flattened form.
//////////////////////////////////////////////////////////////////////////////
static std::vector<std::string> pointer_strings(const json& j)
{
std::vector<std::string> result;
const json flat = j.flatten();
result.reserve(flat.size());
for (auto it = flat.begin(); it != flat.end(); ++it)
{
result.push_back(it.key());
}
return result;
}
static std::vector<json::json_pointer> pointers(const json& j)
{
const std::vector<std::string> strings = pointer_strings(j);
std::vector<json::json_pointer> result;
result.reserve(strings.size());
for (const auto& s : strings)
{
result.emplace_back(s);
}
return result;
}
static void JsonPointerParse(benchmark::State& state, const char* filename)
{
const std::vector<std::string> strings = pointer_strings(load_json(filename));
for (auto _ : state)
{
for (const auto& s : strings)
{
json::json_pointer ptr(s);
benchmark::DoNotOptimize(ptr);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
}
BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerToString(benchmark::State& state, const char* filename)
{
const std::vector<json::json_pointer> ptrs = pointers(load_json(filename));
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
std::string s = ptr.to_string();
benchmark::DoNotOptimize(s);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerAt(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
const std::vector<json::json_pointer> ptrs = pointers(j);
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
const json& value = j.at(ptr);
benchmark::DoNotOptimize(&value);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
static void JsonPointerContains(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
const std::vector<json::json_pointer> ptrs = pointers(j);
for (auto _ : state)
{
for (const auto& ptr : ptrs)
{
bool result = j.contains(ptr);
benchmark::DoNotOptimize(result);
}
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
}
BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
//////////////////////////////////////////////////////////////////////////////
// conversions from and to C++ containers
//////////////////////////////////////////////////////////////////////////////
static void GetVectorInt(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto v = j.get<std::vector<std::int64_t>>();
benchmark::DoNotOptimize(v);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json");
static void GetVectorDouble(benchmark::State& state, const char* filename)
{
const json j = load_json(filename);
for (auto _ : state)
{
auto v = j.get<std::vector<double>>();
benchmark::DoNotOptimize(v);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json");
// a large flat object: the flattened document, one member per scalar
static void GetMap(benchmark::State& state, const char* filename)
{
const json j = load_json(filename).flatten();
for (auto _ : state)
{
auto m = j.get<std::map<std::string, json>>();
benchmark::DoNotOptimize(m);
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
}
BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
/// 100,000 strings, each too long for the small string optimization
static std::vector<std::string> make_strings()
{
std::vector<std::string> result;
result.reserve(100000);
for (std::size_t i = 0; i < 100000; ++i)
{
result.push_back("a string that does not fit in place: " + std::to_string(i));
}
return result;
}
static void ToJsonStrings(benchmark::State& state, const bool move)
{
const std::vector<std::string> strings = make_strings();
for (auto _ : state)
{
state.PauseTiming();
std::vector<std::string> input = strings;
state.ResumeTiming();
// destroyed inside the timed section, like the copies above
auto* j = move ? new json(std::move(input)) : new json(input);
delete j;
}
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
}
BENCHMARK_CAPTURE(ToJsonStrings, copy, false);
BENCHMARK_CAPTURE(ToJsonStrings, move, true);
// conversion of a JSON array into standard containers // conversion of a JSON array into standard containers
template<typename Container> template<typename Container>
static void GetArray(benchmark::State& state) static void GetArray(benchmark::State& state)
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff. Show more