mirror of
https://github.com/nlohmann/json.git
synced 2026-10-25 15:28:54 +00:00
fe5e4018700f62ee8147d5d262bd2d0cd3ee1ee5
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fe5e401870 |
Consolidate duplicated detail helpers and bodies (#5891) (#5901)
* Add shared detail helpers for the #5891 cleanup Add detail::is_primitive(value_t), detail::hex4_lower(), detail::utf8_shape(), detail::input_format_name(), detail::empty_input_message(), the exception ids 116, 319, 320 and 416 to detail::exception_id, and make detail::is_ascii_copyable() constexpr. Later commits switch the duplicated code over to them. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.4: BSON writers share one write-pass UTF-8 sanitizer (#5891) write_bson_entry_header and write_bson_string re-derived the error-handler policy differently. Both now use sanitize_utf8_for_bson_write, which treats strict like keep (the size pass already validated) and sanitizes replace/ignore into the bytes the size pass measured. No context plumbing is needed. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.1: one MessagePack container prefix helper (#5891) Array and object prefixes differed only in the fix marker and the 16/32-bit markers; both now delegate to write_msgpack_container_prefix. Output is byte-identical. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.2: one iterative walker for CBOR and MessagePack (#5891) write_cbor_iterative and write_msgpack_iterative (and their *_value_or_push helpers) were identical but for the head, scalar, and key writers. They are now one template over a small policy struct, so everything inlines. BON8 keeps its own walker (open string run, closing marker), noted in a comment. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.3, 2.1.8: one UBJSON container start helper, shared frame base (#5891) write_ubjson_start_array/_object are now write_ubjson_start_container<IsArray>, keeping the array-only begin()+1 scan and excessive_valueless rule. ubjson_frame derives from binary_container_frame instead of duplicating it. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.4: write_bytes helper for the byte-string write sites (#5891) The 13 oa.write_characters(reinterpret_cast<const CharType*>(x.data()), n) sites now call write_bytes(const void*, size_t); copy_characters sites and the buffer-based writes are untouched. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.1.5: shared object-key writer for CBOR and MessagePack (#5891) write_cbor_key and write_msgpack_key were the same SFINAE pair apart from the string/scalar writers; both now delegate to write_object_key<Walk>. UBJSON key and to_format_length stay as they are. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.10: writer-side format names from input_format_name (#5891) The CBOR/MessagePack/UBJSON/BSON/BJData/BON8 literals in throw_on_discarded and throw_integer_out_of_range calls now come from detail::input_format_name; the texts are identical, and UBJSON vs BJData still follows use_bjdata. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * serializer: constexpr ascii table, hex4_lower, dedupe helpers (#5891) 1.1: build ascii_copyable() at compile time from is_ascii_copyable and test find_ascii_copyable_run against it for every byte. 1.2/2.5.4: use detail::hex4_lower in both \uXXXX writers. 2.1.6: factor the repeated flush/emit blocks in dump_escaped_impl into always-inline helpers. 2.1.7: comment why the escape copy does not use put_buffer. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.6: use string_t-aware to_string/concat in meta() (#5891) Replaces the four std::to_string sites; rendered text unchanged. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: use detail::is_primitive in json.hpp switches (#5891) has_no_children, set_parents, max_size and the two diff walkers. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.1: factor insert()/iterator throw helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.2: share swap() overloads via swap_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.4: factor primitive reset and range check in erase()/range ctor (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.6: share push_back array bodies via push_back_array_impl (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.7: route parse/accept/sax_parse through *_impl helpers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.8: share the vector-returning to_* bodies via to_binary_vector (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.2.10: merge destroy_string/destroy_binary into destroy_pointer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.1/1.7: exceptions: share what_impl, flatten diagnostics switch (#5891) The four create() bodies build their message through one protected static helper; the diagnostics parent walk handles array/object and ignores all other types. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.2/2.4.3/1.7: json_pointer: share slash and escape checks, drop primitive case lists (#5891) The leading-slash and invalid '~' escape checks are shared helpers; the reference-token walks test object and array first and treat everything else as primitive. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7/2.4.9: iter_impl, iteration_proxy: drop primitive case lists, share compare preamble (#5891) Object and array stay the first branches; everything else is the primitive case. operator== and operator< share the different-container check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5/2.4.6: from_json: share get_number_value and reserve_if_possible (#5891) The three number cases of get_arithmetic_value and the arithmetic from_json share one helper (boolean stays accepted only by the latter); the array and object reserve overload pairs become one. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.8: ordered_map: share erase_impl between the key erase overloads (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.7: json_pointer: flatten walk drops the primitive case list (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.2/1.3/1.5/2.5.1/2.5.2/2.5.5: share helpers in the view code (#5891) - detail::exception_id at every numeric id; throw_* helpers take it - hex_byte, hex4_lower, utf8_shape replace local hex/table code - throw_array_index_out_of_range/_past_end, throw_key_not_found - has_native_float_format replaces the hand-written IEEE check No change of messages, ids or output. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: use utf8_shape in get_bon8_string, add exhaustive UTF-8 validator test (#5891) get_bon8_string takes the valid range of the second byte from utf8_shape() (0xF5..0xF7 stay errors, unget behavior unchanged). validate_one_utf8 keeps its if-chain (hot bulk path); the new test checks decode, validate_one_utf8, utf8_shape, json::accept and the BON8 reader against each other for every lead and second byte. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.1: get_cbor_sized_bytes for the CBOR chunk getters (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.2: get_string_with_length for the plain-order string sites (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.3, 2.3.4, 2.3.10: unsupported_key_type, empty_input_message, input_format_name in the readers (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.7, 2.3.9: read_object_key and leave_container_and_check for CBOR/MessagePack/BON8 (#5891) UBJSON keys (different getter signature) and its container tail stay as is. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.5: json_sax_dom_error_state base for the DOM SAX parsers (#5891) Holds errored, allow_exceptions, parse_error() and is_errored(). The scalar handlers stay in each class (parse hot path). Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.6: table of control-character messages in the lexer (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.3.8: finish_input helper for the strict-EOF block in the parser (#5891) sax_parse keeps its if (result) guard and return value. Also adds the clang-tidy NOLINT for the 2.3.6 message table. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: share parse_array_index between json_pointer and json_view (#5891) Move json_pointer::parse_array_index() and its status enum to namespace detail (json_pointer keeps a forwarding member), and let the json_view pointer resolution use it instead of its own copy of the RFC 6901 index grammar. Both accepted the same tokens with the same outcome. Also update the comment on detail::decode(), which listed four of the five UTF-8 validators and now points to the test that keeps them in sync. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.4.5: keep the inlined number cases in from_json(ArithmeticType&) (#5891) Calling get_number_value() from from_json(ArithmeticType&) made GetArray<std::array<int, 1000>> 2.4x slower (it changes the inlining of the std::array conversion), so that overload keeps its own switch, with a comment. get_arithmetic_value() still uses the shared helper. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 2.5.7: force-inline detail::parse_array_index (#5891) Moving parse_array_index out of json_pointer stopped clang from inlining it into the pointer resolution (JsonPointerAt/citm_catalog +17%, JsonPointerAt/twitter +8%); JSON_HEDLEY_ALWAYS_INLINE restores the baseline. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * 1.5: keep get_bon8_string's own UTF-8 ranges (#5891) Taking the second-byte range from utf8_shape() made BON8 decoding 2-3% slower (FromBinaryShape/scalars, FromBinaryBuffer, alignment-pinned builds), so the byte path keeps its hand-written ranges. The exhaustive validator test still checks that it accepts the same sequences as the other validators. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Amalgamate (#5891) Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix CI failures: constexpr, NRVO, cppcheck, IWYU (#5891) - json_sax_dom_error_state::is_errored() cannot be constexpr: the class is not a literal type (GCC 4.8, GCC -Wpedantic, Clang 3.5). - to_binary_vector: destroy the vector writer before returning, as the output_vector_sink sets the size of the vector in its destructor. Without NRVO (MSVC Debug in C++14 mode), the vector was moved out before it was resized. - parser::sax_parse: return false instead of result after the early exit (cppcheck identicalConditionAfterEarlyExit). - json.hpp: export detail/exceptions.hpp for IWYU, so json_view.hpp can name exception_id without including a detail header. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Address review: throw helpers take input_format_t, simpler type checks - throw_on_discarded() and throw_integer_out_of_range() take an input_format_t and call input_format_name() themselves - has_no_children(), set_parents() and max_size() check array, then object, instead of calling is_primitive() first - parse_impl() chains like accept_impl(); sax_parse_impl() uses if/else - std::array for the control character messages; negation<> in write_object_key(); clarify the is_primitive() comment Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_view: use exception_id in the value() overloads Signed-off-by: Niels Lohmann <mail@nlohmann.me> * json_sax: assert the invariant of place_container for the static analyzer Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
dd7686a269 |
json_view: faster reading, indexing and C-stream parsing (#5888)
* Read C streams into a json_document in blocks json_document::parse(FILE*) (and read(), accept(), parse_copy()) went through the library's file input adapter and appended the text character by character with fgetc. Read it with fread in blocks instead, like the std::istream overload already does. Parsing citm_catalog.json (1.7 MB) from a FILE*: Apple M1: 80.6 M -> 8.4 M instructions, 4.8 ms -> 0.55 ms (parse of the same text from a std::string: 8.7 M) Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 47.0 M -> 13.7 M / 45.8 M -> 14.2 M The parsed documents and the error cases are identical (checked over the test corpus and 30 mutants of each file). A null FILE* is handled as before (by the library's adapter). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Index arrays without subtrees directly operator[](idx), at(idx) and back() skipped the elements of an array one after the other, whatever they were. In an array none of whose elements is an array or object (numbers, strings, booleans, null) the elements are adjacent nodes, which the array node's own sizes tell: its subtree has exactly one node more than it has elements. Such an element is now the node idx after the array node; any other array is walked as before. Reading r[i] for every i of an array of 3000 integers: Apple M1: 28.8 M instructions, 3.1 ms -> 0.1 M instructions, 4 us Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 84.1 M -> 3.1 M / 75.0 M -> 3.0 M Arrays that contain arrays are unchanged. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Skip the bounds checks of number and string tokens in NUL-terminated input For a NUL-terminated input (std::string, C string, string literal), the parser reads the terminator instead of comparing p with the end, via cursor::cur(). Number and string scanning still tested `p != e` before each look at the next byte (after the integer digits for '.', for 'e', after the exponent sign and digits, and after a string for its closing quote), although the terminator can never be one of those characters. They now use cur(), which is the plain load for NUL-terminated input and the bounds check for ptr+len input, as before. Parsing, Apple M1 instructions per parse: twitter 3.126 M -> 3.089 M (-1.2 %), citm_catalog 8.70 M -> 8.58 M (-1.3 %), jeopardy 306.1 M -> 302.5 M (-1.2 %), signed integers 281 M -> 266 M (-5.4 %), canada and floats -0.1 % / -0.9 %. Linux x86-64 Ir, GCC 13 / Clang 18: twitter -0.5 % / -0.0 %, signed integers +0.2 % / -0.7 %. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the index text of items() on arrays lazy basic_json_view::items() on an array formatted every element's index with std::to_string when the item was made, whether or not the loop asks for the key. The item now keeps the position and makes the text on the first key() call. Objects are not affected. Iterating over the items of an array of 3000 numbers and reading only the values: Apple M1: 25.5 us -> 4.6 us (0.45 M -> ~0 net instructions) Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 14.9 M -> 4.1 M / 15.3 M -> 4.6 M Reading key() costs what it did (24.5 us). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Make the default of value() with a C string only if the member is missing basic_json_view::value(key, "text") and value(pointer, "text") built a std::string from the default first and passed it on, so a call allocated for the default even when the member exists, and copied it again when it does not. They now look the member up and make the result directly: from the member if there is one, else from the C string. Behavior, including the exceptions, is the same. Two calls (member found, member missing) with a 40 character default, Apple M1: 1.6 K -> 0.7 K instructions Linux x86-64 Ir (500 rounds), GCC 13 / Clang 18: 2.46 M -> 2.22 M / 2.40 M -> 2.15 M Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Materialize scalars directly and keep the stack of small subtrees local basic_json_view::materialize() (and get<BasicJsonType>()) always set up the SAX handler that parse() uses and a std::vector for the open containers, sized from the document's deepest nesting. For a scalar, which is most of what is materialized one value at a time, neither is needed: the value is made directly (the same constructors the handler ends up calling). For arrays and objects the stack of open containers is now a local array for the first 16 levels, and the vector is only used for deeper documents. Materializing five scalars of a small document, Apple M1: 3.6 K -> 0.9 K instructions Linux x86-64 Ir (500 rounds), GCC 13 / Clang 18: 2.96 M -> 2.37 M / 2.90 M -> 2.29 M Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Copy the text between escapes while scanning it A string that contains an escape is decoded by builder::slow_string(). After each escape it scanned the next run of plain bytes (8 or 16 byte-by-byte steps, then a vector loop), and then copied that run to the decode arena in a second pass. The run is now copied 16 bytes at a time while it is scanned (one vector load, a store of the same 16 bytes, and the mask of the bytes that end the run), and the write position lives in a local variable, not in the builder, since a store of a character may alias the builder's members and made the compiler reload it after every escape. A run that ends at a quote needs no scan, and non-ASCII text goes through the UTF-8 check as before; the portable code is unchanged. Parsing, Apple M1 instructions: jeopardy.json 304.0 M -> 293.8 M (-3.4 %), 3000 strings with escapes 1.72 M -> 1.54 M (-10 %), twitter -0.6 %. Linux x86-64 Ir, GCC 13 / Clang 18: escaped strings -7.6 % / -11.4 %, twitter 0.0 % / 0.0 %. Documents and error messages are identical on 900 k random strings of escapes, UTF-8 and control characters (also under ASan), and on mutants of the test corpus. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Hash the last bytes of a key of a large object without a loop key_hash() mixed a key eight bytes at a time and then packed the remaining 0-7 bytes one by one, in a loop. The last word of a key of eight or more bytes is now read as the last eight bytes of the key (overlapping the word before), and a key of 2-7 bytes as two overlapping words of four or two bytes. A product only moves bits upwards, and the slot is taken from the low bits: the high bits of the last word are folded down before the multiplication, since keys that differ only in their last characters ("user_17", "user_18") would otherwise share slots. The hash is not stored, so its values may change. Looking up every key of an object of 1000 random keys, Apple M1 instructions per 1000 lookups: keys of 5 bytes 175 K -> 93 K, hex strings of 8 bytes 177 K -> 168 K, keys of 12 bytes 196 K -> 186 K Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 5-byte keys -4.7 % / -8.8 %, 12-byte keys -3.3 % / -7.4 %. Building the tables while parsing such objects is cheaper as well (5-byte keys: 13.3 us -> 10.1 us). Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Compare short keys of a large object without a call of memcmp find_indexed() compared the key of a member the table points to with memcmp, a library call for any length. Up to 16 bytes, the bytes are now compared as two overlapping words (the first and the last 8, 4, or 2 bytes), as find_member() already does for the objects without a table; longer keys still use memcmp. Looking up every key of an object of 1000 random keys, Apple M1 instructions per 1000 lookups (with the previous commit): keys of 5 bytes 93 K -> 72 K, hex strings of 8 bytes 168 K -> 148 K, keys of 12 bytes 186 K -> 170 K, keys of 24 bytes 198 K -> 193 K (unchanged, memcmp). Linux x86-64 Ir (whole run), GCC 13 / Clang 18: 5-byte keys -7.2 % / -7.3 %, 12-byte keys -5.9 % / -6.6 %. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Read an unedited editable document like a read-only one Views of a json_editable_document followed edits at every step: the lookup, the element access, back(), dump() and materialize() were built for nodes that can be moved or linked, though no edit changes the index until the first one (every edit creates the edit state before it touches a node). Until then these now take the read-only code: one check of the document's edit state per call, not per member or per node. After the first edit nothing changes. Views that were made earlier keep working, as the check is made when they are used. Unedited editable document, Apple M1: dump() of twitter.json 1.40 M -> 1.22 M instructions (-12.6 %); lookups unchanged. Linux x86-64 Ir, GCC 13 / Clang 18: dump -1.7 % / -1.0 %, lookups -0.1 % / 0.0 %. Walking with iterators is unchanged, and so is dump() after an edit. Belongs to the PR "json_view: read-side speedups". Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Format the numbered test keys of json_view's index with a stream The keys were made with snprintf() and a format from a list, which Flawfinder reports as an error (CWE-134, a format string that is not a literal). A stream now formats them; the 30000 keys of the test are the same as before. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Put the key number of the json_view index test in parentheses astyle formatted i * step after << as a pointer declaration (i* step), which failed the amalgamation check. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Fix the compiler and lint findings of json_view's read side and tests - find() and contains() dispatch on Editable for the member lookup; for documents that cannot be edited both branches were the same call (GCC -Wduplicated-branches) - json_view.hpp includes <cstdio> for the FILE overload (cpplint) - the arena check macro of the builder is on one line (cpplint read it as an if body without braces) - tests: no nested conditional (also MSVC C4018), no useless cast, parentheses around mixed operators, raw string literals, += instead of temporary concatenations, and NOLINT for the fixed seeds and a find() != npos, as elsewhere in the tests Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Grow the decode arena through a function and store its position back on every return slow_string() kept its write position in locals and grew the arena through the macro NLOHMANN_VIEW_ENSURE. The macro is now the always-inline member arena_ensure() (a lambda is not inlined at every call site by clang, which then keeps the position in memory: +11 % instructions on escape-heavy strings), and a guard stores the position back into m_arena_write on every return instead of by hand on the successful one. Instruction counts are unchanged (clang) or 1 % lower (GCC). Also word the complexity of back() as that of at(). Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me> |
||
|
|
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 |
||
|
|
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> |