Compare commits

..
Author SHA1 Message Date
Niels Lohmann 77730529ab Merge remote-tracking branch 'origin/json-view/19-edit-set' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:55 +02:00
Niels Lohmann e0c29a1dc4 Merge remote-tracking branch 'origin/json-view/18-view-object-index' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:51 +02:00
Niels Lohmann 93b772553f Merge remote-tracking branch 'origin/json-view/16-view-simd' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:46 +02:00
Niels Lohmann e4f4c767c0 Merge remote-tracking branch 'origin/json-view/15-view-bench' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:42 +02:00
Niels Lohmann 1d49222f4e Merge remote-tracking branch 'origin/json-view/14b-view-float-layout' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:37 +02:00
Niels Lohmann 8b04885f59 Merge remote-tracking branch 'origin/json-view/14-view-compare' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:32 +02:00
Niels Lohmann d94a8d0a5a Merge remote-tracking branch 'origin/json-view/13-view-dump' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:28 +02:00
Niels Lohmann dfa17bd446 Merge remote-tracking branch 'origin/json-view/12-view-values' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:23 +02:00
Niels Lohmann 0b6a55ea6b Merge remote-tracking branch 'origin/json-view/11-view-access' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:19 +02:00
Niels Lohmann 5286c2843b Merge remote-tracking branch 'origin/json-view/10-view-document' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:14 +02:00
Niels Lohmann 3ba495ddf9 Merge remote-tracking branch 'origin/json-view/08-view-builder' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:09 +02:00
Niels Lohmann 2739c0b0af Merge remote-tracking branch 'origin/json-view/04-unicode-escapes' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:20:04 +02:00
Niels Lohmann 471b35c5e8 Merge remote-tracking branch 'origin/json-view/03-string-scan' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:19:58 +02:00
Niels Lohmann e87813232f Merge remote-tracking branch 'origin/json-view/02b-float-parser' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:19:20 +02:00
Niels Lohmann 15b0cc0561 Merge remote-tracking branch 'origin/develop' into HEAD
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-04 17:18:51 +02:00
Niels Lohmann f579e7e055 Merge branch 'json-view/19-edit-set' into json-view/20-edit-structure
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:44:17 +02:00
Niels Lohmann 8f185c87f7 Mark an Infer false positive in materialize()
Infer 1.3.0 reports NULLPTR_DEREFERENCE because the node pointer can come from navigation<true>::value(), which follows links. A link always has a target in a valid index, so suppress it on that line, as develop does for its own Infer false positives (#5750).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:44:12 +02:00
Niels Lohmann a43bfe645b Merge branch 'json-view/18-view-object-index' into json-view/19-edit-set
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:44:01 +02:00
Niels Lohmann f6b6aaf69f Merge branch 'json-view/16-view-simd' into json-view/18-view-object-index
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:56 +02:00
Niels Lohmann d62e3ec9c6 Merge branch 'json-view/15-view-bench' into json-view/16-view-simd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:51 +02:00
Niels Lohmann 3cea705804 Merge branch 'json-view/14b-view-float-layout' into json-view/15-view-bench
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:47 +02:00
Niels Lohmann 910fbb4b5c Merge branch 'json-view/14-view-compare' into json-view/14b-view-float-layout
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:42 +02:00
Niels Lohmann c8ec867991 Merge branch 'json-view/13-view-dump' into json-view/14-view-compare
Conflicts in See also lists (docs/mkdocs/docs/api/basic_json/operator_ne.md), where develop (#5638) and this branch both edited: kept develop's entries and added this branch's basic_json_view links.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:37 +02:00
Niels Lohmann 2e06ccf0c7 Merge branch 'json-view/12-view-values' into json-view/13-view-dump
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:33 +02:00
Niels Lohmann f17207377b Mark an Infer false positive in get_impl()
The tests added here instantiate basic_json::get() with a type for which Infer 1.3.0 reports STACK_VARIABLE_ADDRESS_ESCAPE on "return ret;", although ret is returned by value. Suppress it on that line, as develop does for its own Infer false positives (#5750).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:43:28 +02:00
Niels Lohmann 3b30446d1b Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:37:21 +02:00
Niels Lohmann 960d3435a1 Merge branch 'json-view/10-view-document' into json-view/11-view-access
Conflicts in See also lists (docs/mkdocs/docs/api/basic_json/begin.md,docs/mkdocs/docs/api/basic_json/cbegin.md,docs/mkdocs/docs/api/basic_json/cend.md,docs/mkdocs/docs/api/basic_json/end.md,docs/mkdocs/docs/api/basic_json/type_name.md), where develop (#5638) and this branch both edited: kept develop's entries and added this branch's basic_json_view links.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:37:16 +02:00
Niels Lohmann ceb1cdb774 Merge branch 'json-view/08-view-builder' into json-view/10-view-document
Conflicts in the See also lists of nine basic_json pages, is_discarded.md, and features/index.md, where develop (#5638) and this branch both added entries: kept both. Ran make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:36:41 +02:00
Niels Lohmann 80761cf255 Merge branch 'json-view/04-unicode-escapes' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:36:09 +02:00
Niels Lohmann eed512f1d5 Merge branch 'json-view/03-string-scan' into json-view/04-unicode-escapes
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:36:05 +02:00
Niels Lohmann 3f672f036d Merge branch 'json-view/02b-float-parser' into json-view/03-string-scan
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:36:00 +02:00
Niels Lohmann 83302ff69d Merge branch 'develop' into json-view/02b-float-parser
Conflicts:
- number_parse.hpp: kept this branch's float parser, which replaces the
  Eisel-Lemire code that develop's side changed (#5750 made its digit
  counter unsigned; this parser has no such counter, and it compiles
  cleanly with GCC's -Wstrict-overflow=5).
- number_handling.md, template_parameters.md: kept this branch's
  description of the conversion and added develop's "Before version
  3.13.0" sentence.

Ran make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-02 11:35:40 +02:00
Niels Lohmann d3ba92d4fb Merge branch 'json-view/19-edit-set' into json-view/20-edit-structure
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:29:07 +02:00
Niels Lohmann 89d6a7c2b5 Merge branch 'json-view/18-view-object-index' into json-view/19-edit-set
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:28:57 +02:00
Niels Lohmann 95c8d2aa46 Merge branch 'json-view/16-view-simd' into json-view/18-view-object-index
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:28:52 +02:00
Niels Lohmann d38f5f111f Merge branch 'json-view/15-view-bench' into json-view/16-view-simd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:28:48 +02:00
Niels Lohmann 057c86de8c Mark compare.py's subprocess and download calls for Bandit
The commands are argument lists the script builds itself, and the
downloads are pinned https URLs whose SHA-256 is checked.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:28:47 +02:00
Niels Lohmann 448a90909e Merge branch 'json-view/14b-view-float-layout' into json-view/15-view-bench
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:46 +02:00
Niels Lohmann d9e4155ba1 Merge branch 'json-view/14-view-compare' into json-view/14b-view-float-layout
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:39 +02:00
Niels Lohmann 7130880754 Merge branch 'json-view/13-view-dump' into json-view/14-view-compare
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:30 +02:00
Niels Lohmann 89bf08760f Merge branch 'json-view/12-view-values' into json-view/13-view-dump
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:23 +02:00
Niels Lohmann b1595c1b40 Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:13 +02:00
Niels Lohmann 3d6d610fdc Merge branch 'json-view/10-view-document' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:03 +02:00
Niels Lohmann 134b2f0efe Mark json_view.hpp's read() and strlen as Flawfinder false positives
json_document::read is a member function, not POSIX read(), and the C
string overload requires null-terminated input like json::parse.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:27:01 +02:00
Niels Lohmann dddb2d6e43 Merge branch 'json-view/08-view-builder' into json-view/10-view-document
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:26:20 +02:00
Niels Lohmann b59fc6c902 Mark string_ref's strlen as a Flawfinder false positive
string_ref(const char*) requires a null-terminated string, like
std::string_view's constructor.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:26:18 +02:00
Niels Lohmann ad248a3290 Merge branch 'json-view/19-edit-set' into json-view/20-edit-structure
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:20:10 +02:00
Niels Lohmann db73471df4 Merge branch 'json-view/18-view-object-index' into json-view/19-edit-set
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:20:08 +02:00
Niels Lohmann c6ac5c85c2 Merge branch 'json-view/16-view-simd' into json-view/18-view-object-index
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:20:06 +02:00
Niels Lohmann 0e57b3fb28 Merge branch 'json-view/15-view-bench' into json-view/16-view-simd
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:20:04 +02:00
Niels Lohmann d575be3e55 Merge branch 'json-view/14b-view-float-layout' into json-view/15-view-bench
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:20:02 +02:00
Niels Lohmann 94c518f94d Merge branch 'json-view/14-view-compare' into json-view/14b-view-float-layout
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:59 +02:00
Niels Lohmann 76ce7e2c84 Merge branch 'json-view/13-view-dump' into json-view/14-view-compare
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:57 +02:00
Niels Lohmann 5277335a9e Merge branch 'json-view/12-view-values' into json-view/13-view-dump
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:55 +02:00
Niels Lohmann 05b6cd0892 Merge branch 'json-view/11-view-access' into json-view/12-view-values
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:53 +02:00
Niels Lohmann 95d10dab70 Merge branch 'json-view/10-view-document' into json-view/11-view-access
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:19:51 +02:00
Niels Lohmann 371a8a3d9f Merge branch 'json-view/08-view-builder' into json-view/10-view-document
Signed-off-by: Niels Lohmann <mail@nlohmann.me>

# Conflicts:
#	Makefile
#	cmake/ci.cmake
2026-10-01 10:19:49 +02:00
Niels Lohmann ae01d57694 Merge branch 'json-view/03-string-scan' into json-view/04-unicode-escapes
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:18:33 +02:00
Niels Lohmann 6a757ca675 Merge branch 'json-view/02b-float-parser' into json-view/03-string-scan
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:18:33 +02:00
Niels Lohmann cb51f80e34 Merge branch 'json-view/04-unicode-escapes' into json-view/08-view-builder
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 10:18:33 +02:00
Niels Lohmann 9d44e3f359 Merge branch 'develop' into json-view/02b-float-parser
Conflicted only in tests/src/unit-class_lexer.cpp, where develop's #5737
lint fix (CAPTURE(x); -> CAPTURE(x)) collided with this PR's rewrite of
the Eisel-Lemire float tests; kept the PR's new tests and applied the
lint-fixed CAPTURE style. single_include regenerated via make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 08:53:02 +02:00
Niels Lohmann 9d88ead578 Clarify that the strtold fallback substitutes the locale's decimal point
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 07:40:37 +02:00
Niels Lohmann a496c709f4 Document insert() and erase() of editable json_documents
Add API reference pages for basic_json_document::insert and
basic_json_document::erase, matching the style of set.md and
push_back.md: signatures, parameters, return values, exception safety,
exceptions with their exact ids and messages, complexity, notes on
duplicate keys and view/iterator validity, and an example.

Add example programs basic_json_document__insert.cpp and
basic_json_document__erase.cpp with their expected output, each
comparing an edit on an editable document with the same edit on a
plain json value to show what is preserved: member order, the
spelling of untouched numbers, and, for insert, that a view taken
before the insert keeps referring to the same element after its index
shifts.

Register both new pages in mkdocs.yml, docSet.sql, and the member list
of basic_json_document/index.md, add cross-references to them from
set.md and push_back.md, and mention insert/erase in the "Editing a
document" section of features/json_view.md.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:12 +02:00
Niels Lohmann 57bdb2f67f Address the clang-tidy findings of the structural edit tests
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:11 +02:00
Niels Lohmann b6d6d4996a Benchmark editing documents with json_view, yyjson, Boost.JSON, and json
bench_edit.cpp joins the comparison: parse, apply the same logical edits
with each library's own API (a handful at fixed places, or one in every
record), and serialize; all outputs must describe the same value.
compare.py builds and runs it with the other two programs.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:10 +02:00
Niels Lohmann dcc81f43f0 Add insert() and erase() to editable documents
- insert(array, index, value): insert before an element (index <= size)
- erase(object, key): remove all members with the key; returns their
  number
- erase(array, index): remove an element
- erase(json_pointer): remove the member or element a pointer names

The errors are those of basic_json (type_error.307/309,
out_of_range.401/403/405). A view of an erased value keeps its last value,
and views of other values keep referring to them when elements move.

Tests: the differential test now also inserts and erases members and
elements, directly and through JSON pointers; plus the errors, views
across inserts and erasures, duplicate keys, and large objects.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:09 +02:00
Niels Lohmann 467ccfd480 Fix CI: edit tests with GCC on Linux and without exceptions
- ci_test_gcc (Linux x86-64): std::mt19937::result_type is
  std::uint_fast32_t (unsigned long there), so returning it as
  std::uint32_t failed -Werror=conversion; convert explicitly.
  static_cast<std::uint64_t>(18446744073709551615u) and
  static_cast<std::int64_t>(-9223372036854775807 - 1) were useless casts
  there; use std::numeric_limits instead.
- ci_test_noexceptions: exception_of_call() catches outside a
  CHECK_THROWS, so the invalid UTF-8 checks aborted with JSON_NOEXCEPTION;
  compile them only with exceptions.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:08 +02:00
Niels Lohmann 41233a5d41 Document how editable json_documents use the node index
The node index section on the architecture page says how edits are kept
(the edit buffer, moved elements, links, and new nodes), and the feature
page links to it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:07 +02:00
Niels Lohmann 383ce0b040 Cover the edit storage in tests
Test edits of an empty document and assignments through a view of a
value that is no longer part of the document; copy the entries of a
block through deref() (one path for links and values); mark the
4 GiB limit and the returns of find_parent() that no document reaches.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:06 +02:00
Niels Lohmann 877241a51a Label the pages and examples of the editable aliases
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:05 +02:00
Niels Lohmann d04f78864c Document editable json_documents
- API pages for set and push_back of basic_json_document and for the
  editable aliases; the class overviews name the Editable parameter
- type_error.319 on the exceptions page
- the feature page explains editing, and the examples show when it
  helps: a configuration file changed without reformatting it

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:05 +02:00
Niels Lohmann 51ee239b3c Address the clang-tidy findings of the editable documents
Pick the overloads of encode() with a first_true trait instead of
nested conditionals, name the pointer type in the copies of links,
mark the owning pointers of the edit storage, and compare doubles
by their bits in the tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:04 +02:00
Niels Lohmann 6e50766dc3 Add editable documents with set() and push_back()
basic_json_document<BasicJsonType, true> (json_editable_document,
ordered_json_editable_document) can be edited:

- set(view, value): replace a value
- set(object, key, value): assign a member, or add it (a null becomes an
  object); with duplicate keys, the first is assigned and the others go
- set(array, index, value): assign an element
- set(json_pointer, value): the member, element, or ("-", or the size of
  the array) the end of an array a pointer names
- push_back(array, value): append (a null becomes an array)

Values are views (of any document, copied), BasicJsonType values, and
everything BasicJsonType can be constructed from. The source text is never
written, and the parsed index never moves: new values and element
sequences go to storage owned by the document (edit_storage.hpp), so views
stay valid, and a view keeps referring to its value (after an assignment,
it sees the new one). Read-only documents are unchanged; editing one does
not compile.

Errors are those of basic_json where the operation corresponds
(type_error.305/308, out_of_range.401/403/405, parse_error.106/109); a view
of another document is invalid_iterator.202. Strings are checked for UTF-8
when they enter the document, with the type_error.316 that
basic_json::dump() throws for the same string, so that a document only
holds valid UTF-8. Binary values cannot be stored (the new
type_error.319), and edits of 4 GiB or more end with out_of_range.416.
Views of editable and read-only documents compare with each other.

Tests (unit-json_view_edit.cpp): random assignments, member and element
changes, copies within and between documents, and pushes, applied to an
ordered_json_editable_document and to the ordered_json value; after every
edit both must serialize (also indented and with ensure_ascii),
materialize, compare, and read back the same. Further: the errors, strings
that stay valid while the edit arena grows, numbers (NaN, infinities,
extremes; number_format::source), nulls that become containers, the root
replaced, duplicate keys, values of other documents, large objects, and
documents reused with read().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:03 +02:00
Niels Lohmann d4fab0971f Walk the index of json_view through a navigation policy
Preparation for editable documents, without a change in behavior: views and
documents get a template parameter Editable (false by default), and every
walk over the index (iterators, lookups, dump(), materialize()) goes
through detail::view::navigation<Editable>. For read-only documents it is
the plain node array, as before, so they compile without any of the edit
handling. For editable documents it also follows the representation of
edits, which this commit defines:

- node flags `edited` (a string or number token in the edit arena),
  `moved` (the elements of an array/object live in a separate sequence),
  and `is_new` (no source position), and link nodes (kind_link) that
  stand for a value stored elsewhere
- document_data::edit_state: the moved sequences, the storage of new
  values, and the edit arena

materialize() now keeps a frame per open container instead of returning
to the end of a closed one, as the serializer does, so that it can
follow moved sequences. Floats whose token lives in the edit arena (also
"nan", "inf", "-inf") are converted out of line. dump() copies only
strings of the source without escaping, and shrink_to_fit() leaves the
node array in place once there are edits, as they link into it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:02 +02:00
Niels Lohmann eede67ca92 Fix old clang: do not declare the defaulted document_data() noexcept
With the nested struct object_index, clang 4 (and, by the same bug, the
clang 3.x of ci_test_compilers_clang) rejects the explicitly noexcept
defaulted constructor: "default member initializer for 'indexes' needed
within definition of enclosing class 'document_data' outside of member
functions". Nothing depends on the constructor being noexcept, so let it
take the implicit exception specification.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:01 +02:00
Niels Lohmann 82b31f31b8 Fix CI: useless casts of the key hash of the view's object index
GCC -Werror=useless-cast on Linux x86-64 rejects
static_cast<std::size_t>(key_hash(...)): the call returns a
std::uint64_t prvalue, the same type as std::size_t there, while the cast
is needed where std::size_t is 32 bits wide. Store the hash in a variable
and cast that, which GCC does not report.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:02:00 +02:00
Niels Lohmann 798f4c888a Document the hash index of json_view on the architecture page
The node index section says what extra holds for objects and how large
objects are indexed.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:59 +02:00
Niels Lohmann 98258aa3af Address the clang-tidy findings of the object index tests
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:58 +02:00
Niels Lohmann 904c8c6710 Index the large objects of json_view
Lookups in objects are linear, as for ordered_json. Objects with 128
members or more now get a hash table after parsing (open addressing; the
first of duplicate keys is kept, as for the linear search), so that
operator[], at(), find(), contains(), count(), value(), and JSON pointers
take constant time on average in them; the idea of switching to a hash
table for large objects is Boost.JSON's. The parser notes such objects when
it closes them (out of line, so that the parse loop only has a call for
it), and the object node keeps the number of its table.

Looking up each key of an object with 10,000 members: 59.8 ms -> 0.16 ms.
Parsing (json_document::parse, best of 7, separate processes): most files
within 1%; canada +5%, mesh.pretty +3%, citm +3%.

Tests: objects with 127, 128, 129, and 10,000 members (escaped, empty,
and duplicate keys, missing keys, comparisons), nested large objects, and
documents reused with read().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:58 +02:00
Niels Lohmann 1f0c3be6f3 Address the clang-tidy findings of the SIMD scan
Hold the UTF-8 lookup tables in std::array, compute the length of a
sequence without nested conditionals, and use std::array in the tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:56 +02:00
Niels Lohmann c3b51addbf Scan the strings of json_view with NEON and SSE2
Long runs of string bytes are scanned 16 at a time with NEON (AArch64, with
GCC and Clang) and SSE2 (x86-64): both belong to the baseline instruction
sets. A signed compare with 0x20 finds control characters and non-ASCII
bytes at once. Keys keep 16 table checks before the vector loop (their
lengths repeat from record to record, so the branches predict well);
string values have 8, as their lengths vary more.

Non-ASCII text is validated 16 bytes at a time with the "lookup4" check of
simdjson (J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One
Instruction Per Byte", 2021): with NEON, and on x86-64 with SSSE3 if
JSON_VIEW_USE_SSSE3 is defined (SSSE3 is not part of x86-64, and the code
must not depend on the flags of a translation unit). JSON_VIEW_NO_SIMD
selects the portable code. The vector code sits in
detail/view/simd.hpp; the same input is accepted either way.

json_document::parse, best of 7 runs in separate processes (M1 Max):
poet.json (CJK text) -72%, random.json -25%, twitter.json -22%,
gsoc-2018.json -20%, semanticscholar -19%, github_events -11%,
apache_builds -9.5%, canada/citm -5/-6%; lottie +4%, tree-pretty +2.5%.

Tests: every two-byte sequence and three- and four-byte sequences with
continuation bytes at the edges of their ranges, at every offset around
the vector blocks of keys and values, cut short, and long runs of text
with a damaged byte, against json::accept and json::parse. CMake builds
the parser tests again with JSON_VIEW_NO_SIMD, and on x86-64 with
JSON_VIEW_USE_SSSE3 and -mssse3; the macros are documented.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:55 +02:00
Niels Lohmann a91f6f489f Run the json_view comparison on GitHub runners on demand
A workflow runs compare.py with pinned downloads on GitHub-hosted
Ubuntu runners and shows the results as the job summary and as an
artifact: started by hand (workflow_dispatch: x86-64 or AArch64, GCC or
Clang), or when a pull request gets the label "benchmark" (both
architectures, GCC). The label trigger gives numbers before the
workflow is on the default branch, which workflow_dispatch needs.
Shared runners are noisy, so the numbers show where json_view stands on
another architecture; published numbers still need a quiet machine.

compare.py takes the CPU name from lscpu where /proc/cpuinfo has none
(AArch64 Linux), and falls back to the architecture. Checked in Linux
containers (AArch64, Clang 15 and GCC 9, offline with the pinned
archives).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:54 +02:00
Niels Lohmann 9371f2b6c7 Pin the downloads of the json_view comparison
compare.py --download now checks the SHA-256 of the yyjson 0.13.0,
simdjson 4.6.11, and Boost 1.92.0 archives. It unpacks each archive
once (Boost's directory is boost_1_92_0) and, where Python supports it,
with the 'data' filter.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:53 +02:00
Niels Lohmann 9deb721adc Compare json_view with yyjson, simdjson, and Boost.JSON
tests/benchmarks/json_view/ holds the comparison with other libraries,
which is not built by CMake or run by CI:

- bench_view.cpp: parse, traverse, select, and dump of twitter,
  citm_catalog, canada, jeopardy, a single tweet, and a JSON-RPC request,
  with json_view, yyjson, simdjson (DOM and On-Demand), Boost.JSON, and
  json::parse; all engines must agree on every document before anything
  is timed, and run interleaved in every round
- bench_corpus.cpp: parse, traverse, and dump of any list of files
- compare.py: builds both against include/ with the libraries of the
  system (or pinned downloads), runs them, and writes the results with
  what is needed to reproduce them (date, commit, CPU, OS, compiler,
  flags, library versions) to results/<date>-<host>.md and .csv; only the
  Python 3 standard library is used
- README.md: how to run it, what is measured, and which features the
  engines have, so the numbers can be read correctly

Boost.JSON is optional (JSON_VIEW_BENCH_BOOST).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:53 +02:00
Niels Lohmann ecf9df7c45 Convert the floats of json_view from the digit layout
The parser records where the integer digits, the fraction digits, and the
exponent of a float token are. For floats and doubles with at most 19
digits, the value is now read from that layout: the digits eight at a time,
without scanning the token, and rounded by the library's conversion core
(detail::decimal_to_float(): Clinger's fast path where both operands are
exact, else the Eisel-Lemire algorithm, which needs no fallback for up to 19
digits). It rounds correctly, so the values are those of parse(); other
tokens and types keep the library's conversion of the whole token.

get<double>(), materialize(), dump(), and comparisons use it. Traversing
canada.json (111,000 floats, every number converted): 0.95 -> 1.29 GB/s.

Tests add tokens around the limits (19 and 20 digits, 2^53, 10^22, and
those of float) to the bit-for-bit comparison with parse().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:51 +02:00
Niels Lohmann b992e1f76a Format the comparison examples with the pinned astyle
The "check" job runs astyle over the documentation examples once it gets
past the amalgamation step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:50 +02:00
Niels Lohmann a7fa8d04e8 Address the cpplint findings of json_view's comparisons
compare.hpp includes <string> (build/include_what_you_use).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:49 +02:00
Niels Lohmann 677507137b Address the clang-tidy findings of the comparisons
Separate the comparison of discarded values from the other types, so
that the conditional chain has no repeated branch bodies, and mark
the deliberate comparisons of views with empty containers in the
tests.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:49 +02:00
Niels Lohmann 21d90fac1c Document the comparisons of json_view
- API pages for operator== and operator!= of basic_json_view, linked
  both ways with the basic_json pages
- the feature page and the class overview list the comparisons
- the examples show when the view helps: detecting a changed document
  without building json values

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:48 +02:00
Niels Lohmann e842f3a68f Add comparisons to json_view
basic_json_view gains operator== and operator!= with other views and with
basic_json values. Two views are equal if the values parse() would
produce for them are equal by basic_json's operator==: numbers compare by
value across their types, and objects by their members, with duplicate
keys resolved as parse() resolves them (the last value, at the position of
the first key). Objects are compared in member order if the object type
keeps an order (ordered_json), by key otherwise, as basic_json does.
Discarded views compare as discarded basic_json values do, which follows
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON. Nothing is materialized except
single numbers, and the walk is iterative.

Tests compare the results for pairs of 1,200 generated documents (also
written differently: sorted keys, canonical numbers) with those of
basic_json, for json and ordered_json, plus numbers, duplicate keys,
member order, discarded values, and 100,000 levels of nesting.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:47 +02:00
Niels Lohmann bc56ac6d9a Format the dump() example with the pinned astyle
The "check" job runs astyle over the documentation examples once it gets
past the amalgamation step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:46 +02:00
Niels Lohmann bfb2b0cb48 Mark the cases of the view's serializer that tests cannot reach
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:45 +02:00
Niels Lohmann 8cccae029b Address the clang-tidy findings of dump()
The output buffer initializes its members in the initializer list, and the
escaping has no nested conditional operators; the test marks a fixed seed.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:44 +02:00
Niels Lohmann c7e534d41e Document dump() of json_view
- API pages for dump, number_format, and operator<< of basic_json_view,
  linked both ways with the basic_json pages
- the feature page describes document order and number_format::source
- the examples show when the view helps: forwarding part of a message
  and writing numbers exactly as they were read

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:43 +02:00
Niels Lohmann 83d72fd7a7 Add dump() to json_view
basic_json_view::dump(indent, indent_char, ensure_ascii, number_format)
writes the text of a value as 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 and with the
library's scanning kernels, floats with the library's conversion, and
integers copied from the source, where they are canonical except "-0".
With number_format::source, numbers are copied as they appear in the
source ("1.50", "1E2", "-0", all digits of long integers). operator<<
takes the indentation from the stream width, as for basic_json.

The writer (detail/view/serializer.hpp) writes through a raw pointer into
a string sized from the source extent of the value, and walks the index
iteratively, so the nesting depth is limited by memory only.

Tests compare the output of 2,000 generated documents with
ordered_json::dump() for several indentations and ensure_ascii, strings
with every kind of escape, numbers (5,000 random doubles, float as
number_float_t), duplicate keys, 100,000 levels of nesting, and streams.
ViewDump joins the benchmarks.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:42 +02:00
Niels Lohmann da971a52c8 Fix CI: useless cast in the array index check of the view's JSON pointers
GCC -Werror=useless-cast on Linux x86-64 rejected
static_cast<std::uint64_t>((std::numeric_limits<std::size_t>::max)()),
as both are the same type there. Compare without the cast: std::size_t
converts to std::uint64_t implicitly on every platform.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:41 +02:00
Niels Lohmann 4c6a64507b Fix CI: value-initialize a const json_view for clang 3.6
clang 3.6 rejects `const json_view invalid;` (no user-provided default
constructor, CWG 253), as fixed in json-view/10-view-document.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:40 +02:00
Niels Lohmann c62aa8ac67 Fix CI: json_view value tests without exceptions and with GCC
- ci_test_noexceptions: exception_of() and without_path() exist only
  with exceptions (they catch outside a CHECK_THROWS, which aborts with
  JSON_NOEXCEPTION); compile the comparisons of the conversion, value(),
  and JSON pointer errors only with exceptions as well.
- ci_test_gcc: -Werror=unused-result for static_cast<void>(j.contains(p))
  (GCC's warn_unused_result ignores a cast to void); store the result.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:40 +02:00
Niels Lohmann a7256deba3 Inline get() of arithmetic values of json_view
get<T>() of arithmetic types is inlined down to the conversion, so that
its checks of the node kind merge with those of the caller, and reading
an integer needs no call. Traversing every value: citm_catalog -6%,
marine_ik -5%, numbers and twitter -3%, mesh -2.5%, canada -1% (and
more above the float conversion from the digit layout: citm_catalog
-14%, marine_ik -11%).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:39 +02:00
Niels Lohmann 3a4eddf4ac Address the clang-tidy findings of values and JSON pointers
get_string() and number_token() return braced lists; the test compares
floats by their bit patterns instead of with memcmp, uses std::any_of, and
marks a fixed seed, a default member initializer (needed by GCC's
-Weffc++), and a string search.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:38 +02:00
Niels Lohmann 43e2d9c525 Document values and JSON pointers of json_view
- API pages for get, get_to, get_string, number_token, and value of
  basic_json_view; JSON pointer overloads of operator[], at, and
  contains; links both ways with the basic_json pages
- the feature page describes which conversions copy nothing
- the examples show when the view helps: strings without copies, numbers
  exactly as written, and paths into a large text

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:37 +02:00
Niels Lohmann 0650a48659 Add values and JSON pointers to json_view
basic_json_view gains get<T>(), get_to(), value() with keys and JSON
pointers, and operator[], at(), and contains() with JSON pointers, plus
two functions basic_json has no counterpart for:

- get_string(): the string without a copy (a string_view into the source,
  or into the decoded strings for strings with escapes)
- number_token(): the text of a number as it appears in the source

get<T>() converts arithmetic types, strings (also string_view_t),
std::nullptr_t, std::vector, maps with string keys, and views directly;
floats are converted from the digit layout recorded by the parser with the
library's conversion chain, so the values are bit-identical to parse().
Other types, including user types with from_json(), go through
materialize().

The exceptions are those of basic_json, message included. Where const
basic_json has undefined behavior (a missing key or an index out of range
with operator[] and a JSON pointer), the result is a discarded view;
value() returns the default wherever basic_json catches out_of_range, and
contains() never throws. Array indices of JSON pointers follow
json_pointer's rules (parse_error.106/109, out_of_range.404/410).

Tests compare the conversions of 2,000 generated documents, 20,000 float
tokens (double and float, bit for bit), and every JSON pointer of 1,000
documents with basic_json, and the exceptions for malformed pointers.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:36 +02:00
Niels Lohmann 29bb5c48b8 Give code outside basic_json the reference tokens of a json_pointer
detail::json_pointer_access returns the reference tokens of a pointer, so
that code resolving pointers without a basic_json value (such as the
zero-copy view) does not have to parse to_string() again. No change in
behavior.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:35 +02:00
Niels Lohmann e17e23a2b1 Fix CI: json_view access tests without exceptions and on clang 3.6
- ci_test_noexceptions: the element access tests compare the exceptions
  of json_view and basic_json through exception_of(), which catches them
  outside a CHECK_THROWS; with JSON_NOEXCEPTION the first one aborted the
  test. Compile those comparisons only with exceptions.
- clang 3.6: value-initialize a const json_view, as in the tests of
  json-view/10-view-document.
- Format three new documentation examples with the pinned astyle, which
  the "check" job runs once it gets past the amalgamation step.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:34 +02:00
Niels Lohmann c2d7177d30 Address the clang-tidy findings of element access and iteration
Marks the default initializer of the item's index string (needed by GCC's
-Weffc++) and, in the test, an escaped literal and a comparison of find()
with end(), which is what the test is about.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:33 +02:00
Niels Lohmann 08dcdaf9b8 Document element access, lookup, and iteration of json_view
- API pages for operator[], at, front, back, find, contains, count,
  begin, end, cbegin, cend, items, and type_name of basic_json_view,
  linked both ways with the basic_json pages
- the feature page and size() describe document order and duplicate
  keys
- the examples show when the view helps: reading a few fields of a large
  text, probing optional members, and members in source order

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:33 +02:00
Niels Lohmann 7dae258350 Add element access, lookup, and iteration to json_view
basic_json_view gains the read-only access functions of basic_json:
operator[] and at() with keys and indices, front(), back(), find(),
contains(), count(), begin()/end(), items() (with structured bindings from
C++17 on), and type_name(). They throw the exceptions (ids and messages)
that the const functions of basic_json throw; where basic_json has
undefined behavior (operator[] with a missing key or an index out of
range, front()/back() of an empty container), the view returns a
discarded view or throws invalid_iterator.214.

Objects are iterated in document order, and all members are visited. With
duplicate keys, lookups find the first member, so that a lookup can stop
at the first match; parse() keeps the last value. Keys of up to 16 bytes
are compared with two overlapping loads instead of memcmp, and most keys
are rejected by their length alone, from the index.

The iterators and items live in detail/view/iterator.hpp, the lookups in
detail/view/lookup.hpp. Tests compare every element and member of 2,000
generated documents with ordered_json, keys of every length around the
load sizes, the exceptions against const basic_json, and the iterators.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:32 +02:00
Niels Lohmann 7da943c5c4 Name JSON types without a basic_json value
basic_json::type_name() now calls detail::value_type_name(value_t), so
that code which reports types without a basic_json value at hand, such as
the zero-copy view, uses the same names in its exception messages. No
change in behavior.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 21:01:31 +02:00
Niels Lohmann 2d7024eed1 Fix CI: read the json_view.hpp amalgamation config from the pull request
The "check" job (Check amalgamation) runs develop's amalgamate.py and read
all configurations from the develop checkout, where config_json_view.json
does not exist until this stack lands, so it failed with
FileNotFoundError. Read that configuration from the pull request's
checkout; the tool itself stays develop's.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:59:09 +02:00
Niels Lohmann d71a494367 Fix CI: json_view tests without exceptions, on clang 3.6, and single header
- ci_test_noexceptions: the helpers that compare the exceptions of
  json_document::parse() and json::parse() catch them outside a
  CHECK_THROWS, so with JSON_NOEXCEPTION the first parse error aborted
  the test. Compile those comparisons only with exceptions, as
  unit-class_parser.cpp does.
- ci_test_gcc: -Werror=unused-result for CHECK_THROWS_AS(json_document::
  parse(...)); assign the result to a dummy document.
- ci_test_compilers_clang (3.6): `const json_view invalid;` needs a
  user-provided default constructor there (CWG 253); value-initialize it.
- ci_test_single_header: json_view.hpp now exists as a single header and
  contains the internal view headers, so unit-json_view_builder.cpp
  includes it instead of the detail headers in that mode, and the test
  is built again with the single header.
- Regenerate single_include/nlohmann/json_view.hpp for the builder change
  merged from json-view/08-view-builder.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:49 +02:00
Niels Lohmann d9a71e7bc5 Document the node index of json_view on the architecture page
A new section describes the 16-byte node: its fields, how integers,
floats, and object members are stored, how views navigate without
pointers, and a worked example. The feature page and the pages of
basic_json_document and node_count link to it where they mention the
16 bytes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:48 +02:00
Niels Lohmann 20d0723b67 Address the cpplint findings of json_view's materialize()
materialize.hpp includes <string> (build/include_what_you_use).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:48 +02:00
Niels Lohmann 06bebe2af6 Mark the code of json_view that tests cannot reach
The 4 GiB limit and the fallback for an input that parse() accepts but
the view rejects (a bug) are excluded from the coverage; shrink_to_fit()
of an empty document is tested.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:47 +02:00
Niels Lohmann 723cf14be4 Address the clang-tidy findings of json_document and json_view
- the input dispatch takes byte ranges by const reference and reads the
  size once (which also settles a finding of the static analyzer); input
  adapters are taken by value
- the classification of inputs keeps its nested conditional operators, a
  constant expression of C++11 (NOLINT)
- the test's C arrays, fixed seed, and escaped literals are marked, as in
  the other tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:46 +02:00
Niels Lohmann 8c1b230277 Benchmark json_document
benchmarks_view.cpp adds ViewParse, ViewRead (a reused document),
ViewParseIndented, ViewAccept, and ViewMaterialize on the files of
ParseString, so that each row can be read against the json::parse row of
the same file; benchmarks.cpp gains Accept (json::accept) as the
counterpart of ViewAccept. The view benchmarks are built only if the
header directory has json_view.hpp, so that older versions can still be
benchmarked.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:45 +02:00
Niels Lohmann 061c30310e Document json_document and json_view
- API pages for basic_json_document and basic_json_view, one per member,
  and for the four aliases, each with an example
- features/json_view.md: the problem the view solves, ownership and
  lifetime, what matches basic_json::parse() and what differs, and when
  to choose json, ordered_json, SAX, or the view
- the examples show why one would use the view, not only how: borrowed
  vs. owned input, reading a few fields and materializing one subtree,
  reusing a document across many messages
- registered in the mkdocs navigation, llms.txt, the docset, the
  exceptions page (out_of_range.416), architecture.md, the integration
  page, and the README; the yyjson credit is added to the README and
  license.md

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:44 +02:00
Niels Lohmann 2f7d2548e1 Add a fuzzer for json_document
fuzzer-parse_json_view.cpp checks for every input that
json_document::accept agrees with json::accept, that an accepted input
materializes to the value json::parse returns, and that a rejected input
makes both throw the same exception with the same message. It is built
like the other fuzzers (tests/Makefile, and the root Makefile's
fuzz_testing_json_view target, which starts from the JSON test corpus) and
listed in tests/fuzzing.md.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:31 +02:00
Niels Lohmann ad73c78923 Export json_document and json_view from the C++20 module
The nlohmann.json module includes json_view.hpp in its global module
fragment and exports basic_json_document, basic_json_view, and the four
aliases next to basic_json, json, and ordered_json. features/modules.md
lists them, and tests/module_cpp20 parses a document, so that a missing
export fails the ci_module_cpp20 job.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:30 +02:00
Niels Lohmann 08e677ad61 Build, install, and check json_view.hpp
The new single header goes through the same checks and install steps as
json.hpp and json_fwd.hpp:

- cmake/ci.cmake: ci_test_amalgamation regenerates, formats, and compares
  json_view.hpp as well
- check_amalgamation.yml: the pull request check does the same; it runs
  develop's tools, so it needs config_json_view.json on develop first
- meson.build: installs single_include/nlohmann/json_view.hpp
- gen_bazel_build_file.cmake, BUILD.bazel: json_view.hpp joins the
  single-header target; the glob of the other target already covers the
  new headers
- labeler.yml: an "aspect: json_view" label for the header, its
  detail/view headers, tests, and documentation

The CMake install rules for include/ and single_include/, the REUSE
catch-all, and Package.swift cover the new files without changes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:58:15 +02:00
Niels Lohmann d7c6c36979 Test json_document and json_view
unit-json_view.cpp: type queries, size, and empty against basic_json;
materialize() against parse() (json and ordered_json, generated documents,
duplicate keys, 100,000 levels of nesting, parent pointers with
JSON_DIAGNOSTICS); parse errors and their messages equal to parse() for
malformed inputs and all option combinations; the overflow of a float
document (1e39, 3.4028236e38) as in parse(); NUL and BOM; borrowed and
owned inputs (strings, C strings, literals, vectors, string_view, streams,
wide strings, parse_copy, and iterator ranges over pointers, vectors,
strings, and lists); reuse with read(); moves; shrink_to_fit() of the index
and of the decoded strings; source offsets.

unit-json_view_macros.cpp includes the header without
JSON_TEST_KEEP_MACROS, as users do: the view must not depend on the macros
json.hpp undefines, and must not leak its own.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:57:44 +02:00
Niels Lohmann cf352d4ef5 Add json_document and json_view: parse, accept, types, materialize
The public classes of the zero-copy view (#5295), in the new header
<nlohmann/json_view.hpp>:

- basic_json_document<BasicJsonType>: parse (borrowing contiguous byte
  inputs, owning rvalue strings, streams, and other inputs), parse_copy,
  accept, read, root, is_discarded, source, owns_source, node_count,
  memory_usage, shrink_to_fit
- basic_json_view<BasicJsonType>: type and the is_* queries, size, empty,
  materialize (the value parse() would produce, built by the same SAX
  handler), source_offset
- the aliases json_document, json_view, ordered_json_document, and
  ordered_json_view

A parse error throws the exception basic_json::parse would throw for the
same input: the library parser is run on the failing input, so messages,
positions, and exception ids are the same. Inputs of 4 GiB or more are
rejected with out_of_range.416.

The single header single_include/nlohmann/json_view.hpp keeps including
json.hpp; make amalgamate, check-amalgamation, include.zip, and release
handle it.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:57:43 +02:00
Niels Lohmann e96e2982a5 Fix CI: useless cast in the growth of the view's node index
GCC -Werror=useless-cast (ci_test_gcc on Linux x86-64) rejected
static_cast<std::size_t>(guess + (guess / 4) + 64): the sum is a
std::uint64_t prvalue, the same type as std::size_t there, while the cast
is needed where std::size_t is 32 bits wide. Cast a named variable
instead, which GCC does not report. The build stopped at an earlier error
before, so the previous CI run did not show this one.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 0a64e6c99b Fix CI: MSVC C4127 in the view builder and the single-header test build
- msvc (Win32, /W4 /WX) reported C4127 (conditional expression is
  constant) for `TrailingCommas && cur() == ']'` and the like when the
  option is off. Route the template arguments through a static enabled()
  function, as json.hpp's nesting_depth_exhausted() does.
- ci_test_single_header compiled unit-json_view_builder.cpp against
  single_include/, which does not contain the internal
  nlohmann/detail/view headers. Build that test only with the multiple
  headers.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann d8b8c2498f Test the C++11 stand-in for std::string_view of json_view
Six members of string_ref (length, begin, end, operator[], operator!=,
and operator<<) were not reached before C++17, where string_ref is
std::string_view.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 2fb66ba859 Remove the eight-digit case of the view's digit parser
Since whole blocks of eight digits are read directly, parse_upto8() only
gets fewer than eight digits; its eight-digit case was dead code.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann ab49a7b1bf Address the cpplint findings of the view's parser
The exponent of the overflow check is an std::int64_t instead of a long
(runtime/int).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 154f240022 Read whole blocks of eight digits of json_view directly
A block of eight digits of a number token lies inside the input (the
digits were counted while scanning, or are recorded in the digit
layout), so parse_upto19() reads it without the bounds check of the
last, partial block. Traversing canada.json: -11% instructions, -6%
cycles.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 8d3280de1e Classify a NUL inside a string of json_view as a control character
json::parse ends the input at a NUL only between values (where it does
at all); inside a string, a NUL is a control character that must be
escaped. The view reported it as a missing closing quote. (Only the
error code differed: the exception comes from the library parser.)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 118af5015e Test the overflow check of json_view at the largest double
Numbers of more than 19 digits at the boundary of the largest double are
decided by the exact comparison with the midpoint in the overflow check.
The check uses the floating-point type of the document: with float, the
view rejects what parse() rejects (1e39, 3.4028236e38, the midpoint between
the largest float and 2^128), and double documents are not affected.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 9b0b4ff26d Address the clang-tidy findings of the view's parser
- tables as std::array; the frames of the first 64 levels stay a C array
  (not initialized on purpose, NOLINT)
- \u escapes are decoded with the library's hex_codepoint() instead of a
  second table
- the parse failure is private, with an accessor; the special member
  functions of the builder are all declared
- no nested conditional operators; explicit parentheses; a repeated
  branch body merged; auto for casts
- the test's C arrays, fixed seed, and escaped literals are marked, as in
  the other tests

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 694bfd1b4e Keep the arguments of the view's throw helpers used without exceptions
With JSON_NOEXCEPTION, NLOHMANN_VIEW_THROW(e) was std::abort() alone, so
the parameters of the functions that build the exceptions were unused, a
warning that the builds with -Werror turn into an error. The exception is
now evaluated before std::abort(); the program ends anyway.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann ad82821c89 Add the one-pass parser of json_view (internal)
The builder parses JSON text in one pass into the node index: strings and
numbers stay in the source (escaped strings are decoded into an arena),
integers are converted while their digits are in cache, and floats keep
their digit layout for a later conversion. It accepts exactly what
json::parse accepts, for every combination of comments and trailing commas,
with and without a terminating NUL, and with JSON_STRICT_NUL_HANDLING.

Parse state lives in a local cursor whose address never escapes, so that it
stays in registers; out-of-line helpers (errors, regrowth, escapes,
comments) are members of the builder and get the positions they need. The
value dispatch is expanded once for array elements and once for member
values. Literals are compared with memcmp and words read in a fixed byte
order, so nothing depends on the platform's byte order. Error messages come
with the public classes.

Tests (unit-json_view_builder.cpp): accept/reject and values against
json::parse for handwritten, generated, and damaged documents under all
option combinations, from std::string and from exact-size buffers (no read
past the input under AddressSanitizer), deep nesting up to 100,000 levels,
NUL/BOM/whitespace cases, and the test-suite files.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann 1bf0b1b6c2 Add the node index and scanning primitives of json_view (internal)
Internal parts of the zero-copy view (#5295), under detail/view and not
included by json.hpp, so that users of json.hpp compile nothing of it:

- macro_scope.hpp/macro_unscope.hpp: the few macros the view needs, under
  its own prefix (json.hpp undefines its own at its end); the throw macro
  honors JSON_NOEXCEPTION and JSON_THROW_USER like JSON_THROW
- string_ref.hpp: std::string_view from C++17 on, else a small stand-in
- node.hpp: the 16-byte node of the index; its kinds are value_t values
  (checked by a static_assert)
- document_data.hpp: the storage of a parsed document (node array, decode
  arena, owned input)
- scan.hpp: string and digit scanning with unrolled checks at fixed offsets
  (after yyjson) and the library's SWAR and UTF-8 checks, independent of the
  byte order

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann b88e5f9107 Keep the behavior-changing configuration readable after json.hpp
json.hpp undefines JSON_STRICT_NUL_HANDLING and
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON at its end. Code that builds on
the library after it, such as the planned json_view.hpp, reads them from
detail::abi_config instead. The constants live in the ABI namespace, which
already encodes both settings, so they always match the basic_json in use.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:13 +02:00
Niels Lohmann d23803fd32 Decode \u escapes with a table in the lexer
get_codepoint() read the four hex digits of a \u escape with four calls
to get(), each classified by a chain of range comparisons. For contiguous
input, get_codepoint_bulk() now decodes them with one lookup per byte
(hex_codepoint() in string_scan.hpp, after yyjson's read_hex_u16): a
256-entry table maps a byte to its value, or 0xFF for anything else, and
an invalid digit shows in the OR of the four values. It then skips the
four bytes and updates the position counters as four get() calls would.
If a digit is invalid or fewer than four bytes are left, it changes
nothing and the existing loop runs, so errors are reported with the same
message and position as before.

json::parse, best of 5 runs in separate processes (M1 Max): the escaped
twitter.json (every non-ASCII character as \u) -13.6%, all other files
within 0.3%.

Tests compare the contiguous and the streaming path (value or exception
message) for valid escapes, surrogate pairs, truncated and invalid digits
at every position, and 3,000 seeded random escapes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:12 +02:00
Niels Lohmann f1014c938a Fix CI: useless casts to std::size_t in the string-scan tests
GCC -Werror=useless-cast rejected static_cast<std::size_t>(next() % n):
on 64-bit Linux std::uint64_t and std::size_t are the same type, while
the cast is needed where std::size_t is 32 bits wide. Draw the sizes from
a 32-bit value instead, which converts to std::size_t implicitly on every
platform.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:12 +02:00
Niels Lohmann e91fdad877 Find the stop byte of a string run without a byte loop
find_string_special() and find_ascii_copyable_run() test eight bytes at a
time, but located the stopping byte inside a word with a byte loop. The
lowest flagged byte of the SWAR tests is always a true hit (the borrows of
the subtractions can only flag bytes above one), so its index is now the
trailing-zero count of the mask; words are read in little-endian order on
every platform, so this does not depend on the byte order.
scalar_string_bulk_run() validates a run of multi-byte UTF-8 sequences one
after another instead of searching for the next special byte in between,
which helps text in non-Latin scripts.

The kernels serve the lexer's contiguous fast path, the serializer, and the
binary formats. New tests compare all three with byte-by-byte reference
scans on 100,000 generated buffers at three alignments; the portable
fallback of count_trailing_zeros() was checked against the builtin.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:12 +02:00
Niels Lohmann 9c71689715 Convert long doubles under a multi-byte decimal point completely
The strtold fallback, which is left only for long double formats that
are not binary64 (x87, binary128), substituted the first byte of the
locale's decimal point for '.'. Under a locale whose decimal point is
longer than one byte, such as fa_IR.UTF-8 or ar_EG.UTF-8 (U+066B),
strtold stopped there and the value was truncated at the decimal point.
A longer decimal point is now put into a copy of the token.

The test "locale with a multi-byte decimal point" now compares the long
double values with those of the "C" locale; with x87 long doubles it
failed before.

Fixes #5660.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:11 +02:00
Niels Lohmann 44ec53c77b Convert float and double with the library's own correctly rounded parser
float, double, and long double where it is IEEE-754 binary64 (MSVC, Apple
arm64) are now converted by the library itself, correctly rounded and
independent of the locale and of the C and C++ libraries:

- The token is split into sign, significand w (at most 19 digits), and
  decimal exponent q, using the positions of the decimal point and the
  exponent that the scanners already recorded, so no character is
  classified again.
- Clinger's fast path where w and 10^|q| are exact.
- Eisel-Lemire otherwise, now templated for binary32 and binary64.
- For tokens with more than 19 digits whose w and w + 1 round differently,
  an exact big-integer comparison with the midpoint between the two
  candidates (the digit comparison of fast_float, simplified).

This replaces the separate token walks of Clinger's fast path and of
Eisel-Lemire, the significant-digit gate that avoided the former, and, for
float and double, std::from_chars and the locale-aware strtod. std::from_chars
and strtold remain only for other long double formats (x87, binary128,
double-double) and for types that are not IEEE-754. Values are bit-identical
to before wherever the previous conversion was correctly rounded; tokens
converted in a locale with a multi-byte decimal point are now also exact.
Overflow still gives out_of_range.406, underflow a signed zero.

convert_float() is the entry point for other parsers of JSON text: it
converts like the lexer, without allocation for binary32/binary64.

Tests: exact-bit tests for double and float (ties, subnormal and overflow
boundaries, huge exponents, more digits than any midpoint), Eisel-Lemire for
binary32, the round trips of 200,000 doubles and 100,000 floats without
declines, 508 generated hard cases with the expected bits of both formats
(float_hard_cases.hpp) through the converter and both scanners, and
JSON-level overflow/underflow checks for double and float. The locale tests
now check the values in a locale with a multi-byte decimal point.

Docs: the statements that parsing uses strtod/strtof/strtold; the fast_float
credit now names the digit comparison.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 20:19:11 +02:00
122 changed files with 1179 additions and 17862 deletions

No files matched your search

+3 -18
View File
@@ -16,9 +16,6 @@ only_commits:
environment:
matrix:
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
# only build the C++17 variant of each test, split into two jobs each to
# stay below AppVeyor's 60-minute limit per job.
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
configuration: Debug
platform: x86
@@ -37,13 +34,7 @@ environment:
configuration: Release
platform: x86
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
configuration: Release
platform: x86
CXX_FLAGS: "/permissive- /std:c++17 /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
CMAKE_OPTIONS: ""
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
@@ -64,13 +55,7 @@ environment:
configuration: Release
platform: x64
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=0/2"
GENERATOR: Visual Studio 15 2017
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
configuration: Release
platform: x64
CXX_FLAGS: "/permissive- /std:c++17 /Zc:__cplusplus /utf-8 /W4 /WX"
CMAKE_OPTIONS: "-DJSON_TestStandards=17 -DJSON_TestShard=1/2"
CMAKE_OPTIONS: ""
GENERATOR: Visual Studio 15 2017
init:
@@ -81,7 +66,7 @@ install:
- if "%platform%"=="x86" set GENERATOR_PLATFORM=Win32
before_build:
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On %CMAKE_OPTIONS%
- cmake . -G "%GENERATOR%" -A "%GENERATOR_PLATFORM%" -DCMAKE_CXX_FLAGS="%CXX_FLAGS%" -DCMAKE_IGNORE_PATH="C:/Program Files/Git/usr/bin" -DJSON_BuildTests=On "%CMAKE_OPTIONS%"
build_script:
- cmake --build . --config "%configuration%" --parallel 2
+1 -1
View File
@@ -51,7 +51,7 @@ labels:
- "include/nlohmann/detail/view/.*"
- "single_include/nlohmann/json_view\\.hpp"
- "tests/src/unit-json_view.*"
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
- "tests/src/fuzzer-parse_json_view\\.cpp"
- "tests/benchmarks/json_view/.*"
- "tools/amalgamate/config_json_view\\.json"
- "docs/mkdocs/docs/features/json_view\\.md"
+6 -6
View File
@@ -17,11 +17,11 @@ permissions:
contents: read
jobs:
macos-15:
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
macos-14:
runs-on: macos-14 # https://github.com/actions/runner-images/blob/main/images/macos/macos-14-Readme.md
strategy:
matrix:
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1', '26.1.1', '26.2', '26.3']
xcode: ['15.0.1', '15.1', '15.2', '15.3', '15.4']
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
@@ -36,11 +36,11 @@ jobs:
- name: Test
run: cd build ; ctest -j 10 --output-on-failure
macos-26:
runs-on: macos-26 # https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md
macos-15:
runs-on: macos-15 # https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md
strategy:
matrix:
xcode: ['26.4.1', '26.5', '26.6']
xcode: ['16.0', '16.1', '16.2', '16.3', '16.4', '26.0.1']
env:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
+2 -2
View File
@@ -107,7 +107,7 @@ jobs:
container: ubuntu:24.04
strategy:
matrix:
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_delete_deprecated_functions, ci_test_no_thread_local]
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
steps:
- name: Install build-essential
run: apt-get update ; apt-get install -y build-essential unzip wget git
@@ -209,7 +209,7 @@ jobs:
strategy:
matrix:
# older GCC docker images (4, 5, 6) fail to check out code
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', '16', 'latest']
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
container: gcc:${{ matrix.compiler }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
-2
View File
@@ -26,7 +26,6 @@ cc_library(
"include/nlohmann/detail/conversions/from_json.hpp",
"include/nlohmann/detail/conversions/to_chars.hpp",
"include/nlohmann/detail/conversions/to_json.hpp",
"include/nlohmann/detail/conversions/zmij.hpp",
"include/nlohmann/detail/exceptions.hpp",
"include/nlohmann/detail/hash.hpp",
"include/nlohmann/detail/input/binary_reader.hpp",
@@ -73,7 +72,6 @@ cc_library(
"include/nlohmann/detail/view/edit.hpp",
"include/nlohmann/detail/view/edit_storage.hpp",
"include/nlohmann/detail/view/errors.hpp",
"include/nlohmann/detail/view/image.hpp",
"include/nlohmann/detail/view/input.hpp",
"include/nlohmann/detail/view/iterator.hpp",
"include/nlohmann/detail/view/lookup.hpp",
-6
View File
@@ -62,7 +62,6 @@ option(JSON_MultipleHeaders "Use non-amalgamated version of the l
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
option(JSON_StrictBinaryUTF8 "Build with UTF-8 checks in the CBOR, UBJSON, BJData, and BSON writers enabled." OFF)
option(JSON_DeleteDeprecatedFunctions "Delete the deprecated functions instead of only deprecating them." OFF)
if (JSON_CI)
include(ci)
@@ -124,10 +123,6 @@ if (JSON_StrictBinaryUTF8)
message(STATUS "Strict UTF-8 checks in binary writers enabled (JSON_STRICT_BINARY_UTF8=1)")
endif()
if (JSON_DeleteDeprecatedFunctions)
message(STATUS "Deprecated functions are deleted (JSON_DELETE_DEPRECATED_FUNCTIONS=1)")
endif()
if (JSON_Diagnostic_Positions)
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
endif()
@@ -164,7 +159,6 @@ target_compile_definitions(
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
$<$<BOOL:${JSON_StrictBinaryUTF8}>:JSON_STRICT_BINARY_UTF8=1>
$<$<BOOL:${JSON_DeleteDeprecatedFunctions}>:JSON_DELETE_DEPRECATED_FUNCTIONS=1>
)
target_include_directories(
-1
View File
@@ -1399,7 +1399,6 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
- The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2008-2009 [Björn Hoehrmann](https://bjoern.hoehrmann.de/) <bjoern@hoehrmann.de>
- The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/)
- The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut)
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright &copy; 2021 The fast_float authors
+2 -16
View File
@@ -249,20 +249,6 @@ add_custom_target(ci_test_strict_nul_handling
COMMENT "Compile and test with strict NUL-byte handling enabled"
)
###############################################################################
# Delete the deprecated functions.
###############################################################################
add_custom_target(ci_test_delete_deprecated_functions
COMMAND ${CMAKE_COMMAND}
-DCMAKE_BUILD_TYPE=Debug -GNinja
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DeleteDeprecatedFunctions=ON
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions
COMMAND cd ${PROJECT_BINARY_DIR}/build_delete_deprecated_functions && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
COMMENT "Compile and test with the deprecated functions deleted"
)
###############################################################################
# Disable global UDLs.
###############################################################################
@@ -376,7 +362,7 @@ add_custom_target(ci_test_coverage
# Sanitizers.
###############################################################################
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base -fsanitize-ignorelist=${PROJECT_SOURCE_DIR}/cmake/clang_sanitizer_ignorelist.txt")
set(CLANG_CXX_FLAGS_SANITIZER "-g -O1 -fsanitize=address -fsanitize=undefined -fsanitize=integer -fsanitize=nullability -fno-omit-frame-pointer -fno-sanitize-recover=all -fno-sanitize=unsigned-integer-overflow -fno-sanitize=unsigned-shift-base")
add_custom_target(ci_test_clang_sanitizer
COMMAND CXX=${CLANG_TOOL} CXXFLAGS=${CLANG_CXX_FLAGS_SANITIZER} ${CMAKE_COMMAND}
@@ -722,7 +708,7 @@ ci_get_cmake(4.0.0 CMAKE_4_0_0_BINARY)
# the tests require CMake 3.13 or later, so they are excluded for CMake 3.5.0
set(JSON_CMAKE_FLAGS_3_5_0 JSON_Diagnostics JSON_Diagnostic_Positions JSON_GlobalUDLs JSON_ImplicitConversions JSON_DisableEnumSerialization
JSON_LegacyDiscardedValueComparison JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind
JSON_StrictNulHandling JSON_StrictBinaryUTF8 JSON_DeleteDeprecatedFunctions)
JSON_StrictNulHandling JSON_StrictBinaryUTF8)
set(JSON_CMAKE_FLAGS_3_31_6 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
set(JSON_CMAKE_FLAGS_4_0_0 JSON_BuildTests ${JSON_CMAKE_FLAGS_3_5_0})
-8
View File
@@ -1,8 +0,0 @@
# Sanitizer ignore list for ci_test_clang_sanitizer (-fsanitize-ignorelist).
#
# libstdc++ 14's <format> declares `_Scanner(basic_string_view<_CharT>, size_t __nargs = -1)`, so every std::format
# call converts -1 to size_t, which -fsanitize=integer reports as implicit-integer-sign-change. This is
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=119429, not a bug in this library. Only that check and only <format> are
# excluded, so implicit sign changes in the library and the tests are still reported.
[implicit-integer-sign-change]
src:*/include/c++/*/format
-4
View File
@@ -138,7 +138,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::accept',
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::erase', 'Method', 'api/basic_json_document/erase/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::insert', 'Method', 'api/basic_json_document/insert/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::is_discarded', 'Method', 'api/basic_json_document/is_discarded/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::load', 'Function', 'api/basic_json_document/load/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::memory_usage', 'Method', 'api/basic_json_document/memory_usage/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::node_count', 'Method', 'api/basic_json_document/node_count/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::owns_source', 'Method', 'api/basic_json_document/owns_source/index.html');
@@ -147,7 +146,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::parse_co
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::push_back', 'Method', 'api/basic_json_document/push_back/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::read', 'Method', 'api/basic_json_document/read/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::root', 'Method', 'api/basic_json_document/root/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::save', 'Method', 'api/basic_json_document/save/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::set', 'Method', 'api/basic_json_document/set/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::shrink_to_fit', 'Method', 'api/basic_json_document/shrink_to_fit/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('basic_json_document::source', 'Method', 'api/basic_json_document/source/index.html');
@@ -291,7 +289,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('Supported Macros', 'Guide', '
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_ASSERT', 'Macro', 'api/macros/json_assert/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_BRACE_INIT_COPY_SEMANTICS', 'Macro', 'api/macros/json_brace_init_copy_semantics/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'api/macros/json_throw_user/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DELETE_DEPRECATED_FUNCTIONS', 'Macro', 'api/macros/json_delete_deprecated_functions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html');
@@ -322,7 +319,6 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_TRY_USER', 'Macro', 'api
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_GLOBAL_UDLS', 'Macro', 'api/macros/json_use_global_udls/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_IMPLICIT_CONVERSIONS', 'Macro', 'api/macros/json_use_implicit_conversions/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON', 'Macro', 'api/macros/json_use_legacy_discarded_value_comparison/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS', 'Macro', 'api/macros/json_use_objects_for_enum_keyed_maps/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('JSON_USE_SIMDUTF', 'Macro', 'api/macros/json_use_simdutf/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('Macros', 'Macro', 'api/macros/index.html');
INSERT INTO searchIndex(name, type, path) VALUES ('NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE', 'Macro', 'api/macros/nlohmann_define_derived_type/index.html');
+2 -2
View File
@@ -8,7 +8,7 @@ static basic_json diff(const basic_json& source,
Creates a [JSON Patch](http://jsonpatch.com) so that value `source` can be changed into the value `target` by calling
[`patch`](patch.md) function.
For two JSON values `source` and `target`, the following code always yields `#!cpp true`:
For two JSON values `source` and `target`, the following code yields always `#!cpp true`:
```cpp
source.patch(diff(source, target)) == target;
```
@@ -27,7 +27,7 @@ a JSON patch to convert the `source` to `target`
## Exception safety
Strong guarantee: `source` and `target` are never modified.
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
## Complexity
-5
View File
@@ -62,9 +62,6 @@ Linear.
## Notes
Floating-point numbers are written with the fewest digits that read back as the same value (for `#!cpp double`; see
[number handling](../../features/types/number_handling.md#number-serialization)).
Binary values are serialized as an object containing two keys:
- "bytes": an array of bytes as integers
@@ -102,5 +99,3 @@ Binary values are serialized as an object containing two keys:
- Error handlers added in version 3.4.0.
- Serialization of binary values added in version 3.8.0.
- Error handler `keep` added in version 3.13.0.
- Doubles are written with the shortest digits (Żmij instead of Grisu2) since version 3.13.0; about 0.1% of doubles are
written differently, most of them with fewer digits.
@@ -120,12 +120,3 @@ Linear in the size of the input.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
- Added `error_handler` parameter in version 3.13.0.
!!! warning "Deprecation"
- Overload (2) replaces calls to `from_bjdata` with a pointer and a length as first two parameters, which has been
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
`#!cpp from_bjdata(ptr, len, ...);` with `#!cpp from_bjdata(ptr, ptr+len, ...);`.
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
function.
@@ -106,12 +106,3 @@ Linear in the size of the input.
## Version history
- Added in version 3.13.0.
!!! warning "Deprecation"
- Overload (2) replaces calls to `from_bon8` with a pointer and a length as first two parameters, which has been
deprecated in version 3.13.0. This overload will be removed in version 4.0.0. Please replace all calls like
`#!cpp from_bon8(ptr, len, ...);` with `#!cpp from_bon8(ptr, ptr+len, ...);`.
You should be warned by your compiler with a `-Wdeprecated-declarations` warning if you are using a deprecated
function.
@@ -56,10 +56,6 @@ This implementation does exactly follow this approach, as it uses double precisi
smaller than `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally
and be serialized to `null`.
During deserialization (from JSON text or any of the binary formats), a finite number that does not fit into
`number_float_t` is rejected with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406), for
example a double-precision number in a binary format when `number_float_t` is `#!cpp float`.
### Storage
Floating-point number values are stored directly inside a `basic_json` type.
@@ -47,9 +47,8 @@ With the default values for `NumberIntegerType` (`std::int64_t`), the default va
When the default type is used, the maximal integer number that can be stored is `9223372036854775807` (INT64_MAX) and
the minimal integer number that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers that are out of
range will yield over/underflow when used in a constructor. During deserialization (from JSON text or any of the binary
formats), too large or small integer numbers will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md)
or [`number_float_t`](number_float_t.md).
range will yield over/underflow when used in a constructor. During deserialization, too large or small integer numbers
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
@@ -48,9 +48,8 @@ With the default values for `NumberUnsignedType` (`std::uint64_t`), the default
When the default type is used, the maximal integer number that can be stored is `18446744073709551615` (UINT64_MAX) and
the minimal integer number that can be stored is `0`. Integer numbers that are out of range will yield over/underflow
when used in a constructor. During deserialization (from JSON text or any of the binary formats), too large or small
integer numbers will automatically be stored as [`number_integer_t`](number_integer_t.md) or
[`number_float_t`](number_float_t.md).
when used in a constructor. During deserialization, too large or small integer numbers will automatically be stored
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
+1 -5
View File
@@ -68,8 +68,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- 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
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to BJData"`
## Complexity
@@ -121,6 +119,4 @@ Linear in the size of the JSON value `j`.
- BJData version parameter (for draft3 binary encoding) added in version 3.12.0.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
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`.
- 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.
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
@@ -58,9 +58,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key is
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)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if a value nested in `j` is discarded
(the top-level value itself is covered by `type_error.317` above, since it must be an object); example:
`"cannot serialize discarded value to BSON"`
## Complexity
@@ -113,8 +110,6 @@ pass before anything is written.
- Throws `out_of_range.412` and `out_of_range.415` since version 3.13.0.
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
- `out_of_range.415` is now detected before anything is written, like the other exceptions above, since version 3.13.0.
- Throws `type_error.321` for a discarded value nested in `j` since version 3.13.0; previously, it was silently
skipped, producing a document whose declared size did not match what was actually written.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
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
@@ -49,8 +49,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- 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
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to CBOR"`
## Complexity
@@ -88,5 +86,3 @@ Linear in the size of the JSON value `j`.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
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`.
- 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.
@@ -54,8 +54,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
`"subtype 70000 is too large for the MessagePack ext type (max 255)"`
- 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`
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to MessagePack"`
## Complexity
@@ -110,5 +108,3 @@ Linear in the size of the JSON value `j`.
- Fixed in version 3.13.0 to serialize `number_integer_t`/`number_unsigned_t` pairs of different width correctly;
before, integers could be serialized with the wrong value if `number_integer_t` was narrower than
`number_unsigned_t`.
- 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.
@@ -61,8 +61,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
- 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
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
discarded; example: `"cannot serialize discarded value to UBJSON"`
## Complexity
@@ -114,5 +112,3 @@ Linear in the size of the JSON value `j`.
- Added `error_handler` parameter in version 3.13.0. Its default, `keep`, writes the bytes of a string or object key
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`.
- 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 UBJSON.
@@ -52,16 +52,10 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
## Member functions
- [(constructor)](basic_json_document.md)
### Parsing
- [**parse**](parse.md) (_static_) - deserialize from a compatible input, borrowing or owning it as appropriate
- [**parse_copy**](parse_copy.md) (_static_) - deserialize a copy of a compatible input
- [**accept**](accept.md) (_static_) - check whether the input is valid JSON
- [**read**](read.md) - (re-)parse into this document, reusing its memory
### Access
- [**root**](root.md) - the view of the root value
- [**is_discarded**](is_discarded.md) - return whether the last parse failed
- [**source**](source.md) - the parsed text
@@ -69,14 +63,6 @@ bookkeeping edits need, and calling any of them on one fails to compile (`#!cpp
- [**node_count**](node_count.md) - the number of index entries (values plus object keys)
- [**memory_usage**](memory_usage.md) - the number of bytes held by the document
- [**shrink_to_fit**](shrink_to_fit.md) - release unused index capacity
### Images
- [**save**](save.md) - the document as an image that `load()` reads without parsing
- [**load**](load.md) (_static_) - read an image written by `save()`
### Edits
- [**set**](set.md) - replace a value, or set an object member, an array element, or the value a JSON pointer refers
to (`#!cpp Editable` documents only)
- [**push_back**](push_back.md) - append to an array (`#!cpp Editable` documents only)
@@ -1,169 +0,0 @@
# <small>nlohmann::basic_json_document::</small>load
```cpp
// (1)
static basic_json_document load(const std::uint8_t* image, std::size_t size,
const image_check check = image_check::full);
// (2)
static basic_json_document load(const std::vector<std::uint8_t>& image,
const image_check check = image_check::full);
// (3)
static basic_json_document load(std::vector<std::uint8_t>&& image,
const image_check check = image_check::full);
```
1. Reads an image [`save()`](save.md) wrote, from a pointer and a byte count. The image is **borrowed**: `image`
must stay alive and unchanged for as long as the returned document, and any view taken from it, is used.
2. Reads an image from a `#!cpp std::vector`. Also **borrowed** -- equivalent to overload 1 called with
`#!cpp image.data()` and `#!cpp image.size()`.
3. Reads an image, keeping the vector instead of copying it: `image` is moved into the document (no copy), which
then owns it for as long as it needs the text and the decoded strings. [`owns_source()`](owns_source.md) is
`#!cpp true` afterward.
In every overload, the node index is copied into storage the document itself owns -- so that it is properly aligned,
and, for an [editable](index.md#edits) document, can be edited -- while the text and the decoded strings stay in
`image`. The hash indexes [large objects](../../features/json_view.md) use for lookup are rebuilt, exactly as after
parsing.
## Parameters
`image` (in)
: the image [`save()`](save.md) wrote (overloads 1 and 2), or one to take ownership of (overload 3)
`size` (in)
: the number of bytes at `image` (overload 1)
`check` (in)
: how thoroughly to validate `image` before trusting it; see [`image_check`](#image_check) below (optional,
`#!cpp image_check::full` by default)
## Return value
The document read from the image.
## Exception safety
Overloads 1 and 2 give the strong guarantee: `image` is only read, never written, so a thrown exception leaves the
caller's buffer untouched.
Overload 3 moves `image` into the document *before* validating it, so that a good image is kept without a copy. If
loading then fails, the partially built document -- and the vector now inside it -- is discarded along with the
exception, and `image` itself is left **empty**, not restored to what was passed in. Move a copy in instead, or
validate with overload 2 first, if the original vector must survive a failed load.
## Exceptions
On a big-endian target, throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) -- the same
exception [`save()`](save.md#exceptions) throws there, since the image format is little-endian only.
Otherwise throws [`parse_error.116`](../../home/exceptions.md#jsonexceptionparse_error116) if `image` is not one
`save()` could have written, or fails the requested `check`:
| message | when |
|------------------------|------------------------------------------------------------------------------------------------------|
| `too short` | `image` is `#!cpp nullptr`, or `size` is smaller than the 64-byte header |
| `unknown format` | the header's magic bytes or version do not match, or a reserved header field is not zero |
| `sizes out of range` | the node count, text size, or decoded-string size the header describes does not fit `size`, or the `#!cpp '\0'` after the text or after the decoded strings is missing |
| `the check failed` | `check` is not `#!cpp image_check::none`, and the image fails it -- see [`image_check`](#image_check) |
!!! failure "Example messages"
```
[json.exception.parse_error.116] parse error: invalid json_document image: too short
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: unknown format
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: sizes out of range
```
```
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
```
## Complexity
Linear in the number of nodes, which are always copied into the document. With `#!cpp check == image_check::full`,
additionally linear in the combined length of the text and the decoded strings; `#!cpp image_check::bounds` and
`#!cpp image_check::none` do not read them.
## Notes
**The `image_check` modes.**
```cpp
using image_check = detail::view::image_check;
enum class image_check
{
full,
bounds,
none
};
```
How thoroughly `load()` validates `image` before trusting it.
| value | checks | guarantees |
|----------|--------------------------------------------------------------------------------------------------------------|------------|
| `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 | reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document |
| `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 |
| `none` | nothing | images from a trusted source only -- reading a damaged image is undefined behavior |
`full` is the default and 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. `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; it still cannot crash or read out of bounds. `none` skips validation entirely and
should only be used 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
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
[`parse()`](parse.md) borrowed its input for. Overload 3 avoids this by keeping the vector itself; see
[`owns_source`](owns_source.md).
!!! warning "Experimental"
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
stable; `load()` already rejects an image written by a different format version with `parse_error.116`
("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.
**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.
## Examples
??? example "Caching a document, ownership, and a rejected image"
The example below saves a parsed document as an image, checks that `load()` reproduces the original
[`dump()`](../basic_json_view/dump.md) without parsing, and shows the difference between
`load(std::move(image))` (owned) and `load(image)` (borrowed). It then damages one byte of the image and shows
`image_check::full` rejecting it with `parse_error.116`, while `image_check::bounds` -- meant for a cache the
process already trusts -- still reads it without going out of bounds.
```cpp
--8<-- "examples/basic_json_document__load.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__load.output"
```
## See also
- [save](save.md) - write the document as an image
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
- [parse](parse.md) - deserialize from JSON text instead of an image
- [Images](../../features/json_view.md#images) - why and when to use images
## Version history
- Added in version 3.13.0.
@@ -132,7 +132,6 @@ integer type becomes a floating-point value.
- [accept](accept.md) - check whether the input is valid JSON
- [read](read.md) - (re-)parse into this document, reusing its memory
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
- [load](load.md) - read a document from an image instead of parsing JSON text
- [`BasicJsonType::parse`](../basic_json/parse.md) - the corresponding function of `basic_json`
## Version history
@@ -49,11 +49,6 @@ whether or not the new parse succeeds; take fresh views from [`root()`](root.md)
`input` is borrowed or owned by the same rules as [`parse()`](parse.md#notes); a document can borrow on one call and
own on the next, since ownership is decided freshly each time.
Reusing a document matters most for large inputs: the operating system provides the memory of a fresh node index one
page at a time, and every page costs a page fault the first time it is written. On x86-64 Linux (4 KiB pages), parsing
a 55 MB document into a reused document took about 40 % less time than parsing it into a fresh one. Programs that parse
many documents of similar size should therefore keep one document and call `read()`.
## Examples
??? example
@@ -75,7 +70,6 @@ many documents of similar size should therefore keep one document and call `read
- [parse](parse.md) - deserialize from a compatible input
- [root](root.md) - the view of the root value
- [load](load.md) - read a document from an image instead of parsing JSON text
## Version history
@@ -1,104 +0,0 @@
# <small>nlohmann::basic_json_document::</small>save
```cpp
std::vector<std::uint8_t> save() const;
```
Writes the document as an *image*: a byte buffer that [`load`](load.md) reads back without parsing. The image holds
the node index, the source text (plus, for an edited document, the number tokens edits wrote), and the decoded
strings (plus the strings edits wrote) -- everything [`root()`](root.md) needs, with nothing left to parse.
An edited document is written in its *current* state, with its values in document order, the way the library's own
parser would have produced them for that JSON text: a member [`set`](set.md) added goes at the end, an
[`erase`](erase.md)d member leaves no trace, and a float that is not finite (NaN or positive/negative infinity)
becomes null, the same substitution [`dump()`](../basic_json_view/dump.md) makes. The same document always saves to
the same bytes -- also across `BasicJsonType` and `#!cpp Editable`, since the image reflects document order and
values only, not which specialization produced them.
## Return value
The image, as a `#!cpp std::vector<std::uint8_t>`. Pass it, or a pointer to its data together with its size, to
[`load`](load.md) to read the document back.
## Exception safety
Strong guarantee: `save()` does not modify `#!cpp *this` (it is `#!cpp const`), so if it throws, the document is left
exactly as it was, and the partially built image is discarded with the exception.
## Exceptions
Throws [`type_error.320`](../../home/exceptions.md#jsonexceptiontype_error320) if the document is
[discarded](is_discarded.md) -- a default-constructed document, or one a failed [`parse()`](parse.md)/
[`read()`](read.md) with `allow_exceptions == false` left discarded.
On a big-endian target, throws `type_error.320` with a different message instead: the image format is little-endian
only (see [Notes](#notes)).
Throws [`out_of_range.416`](../../home/exceptions.md#jsonexceptionout_of_range416) if the node count, the text, or
the decoded strings of the image would individually reach 4 GiB -- the same 32-bit offsets
[`parse()`](parse.md#exceptions) and, for edits, [`set`](set.md)/[`push_back`](push_back.md) are already limited to.
!!! failure "Example messages"
```
[json.exception.type_error.320] cannot save a discarded json_document
```
```
[json.exception.type_error.320] json_document images need a little-endian target
```
```
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
```
## Complexity
Linear in the size of the document: the number of nodes, plus the length of the text and the decoded strings that end
up in the image.
## Notes
**Format.** The image begins with a 64-byte header (the magic bytes `#!cpp "NJVI"`, a version number, the node count,
and the sizes of the text and the decoded strings, all little-endian), followed by the nodes
([16 bytes each](../../home/architecture.md#node-index-of-json-views)), the text and a `#!cpp '\0'`, and the decoded
strings and a `#!cpp '\0'`. [`load`](load.md) checks the header, and the sizes it describes, before reading anything
else -- see [`load`'s Exceptions](load.md#exceptions).
!!! warning "Experimental"
The image format is versioned but not yet stable: it may change in an incompatible way before it is declared
stable. 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. Keep the original
JSON text if you need to read a saved document back with a future library version.
**Little-endian only.** The image is written as raw little-endian bytes, with no byte-swapping. `save()` (and
[`load`](load.md)) throw `type_error.320` on a big-endian target rather than silently produce bytes a big-endian
reader could not interpret correctly.
## Examples
??? example "Caching a parsed document as an image"
The example below saves a parsed configuration as an image -- the way a service might cache one to answer later
requests without parsing the text again -- and confirms that loading it back gives exactly the same result as
parsing did, and that saving is deterministic.
```cpp
--8<-- "examples/basic_json_document__save.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__save.output"
```
## See also
- [load](load.md) - read an image written by `save()`
- [owns_source](owns_source.md) - return whether the document holds its own copy of the text
- [`basic_json_view::dump`](../basic_json_view/dump.md) - serialize the document to JSON text instead of an image
- [Images](../../features/json_view.md#images) - why and when to use images
## Version history
- Added in version 3.13.0.
-7
View File
@@ -60,13 +60,6 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums
- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference
- [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions
- [**JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS**](json_use_objects_for_enum_keyed_maps.md) - opt in to storing maps with enum
keys as objects
## Deprecated functions
- [**JSON_DELETE_DEPRECATED_FUNCTIONS**](json_delete_deprecated_functions.md) - opt in to deleting the deprecated
functions ahead of their removal in version 4.0.0
## Comparison behavior
@@ -1,96 +0,0 @@
# JSON_DELETE_DEPRECATED_FUNCTIONS
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS /* value */
```
When defined to `1`, all [deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) of the
library are declared as deleted (`= delete`) instead of only being marked as deprecated. Code that still calls one of
them no longer compiles. This way, you can find all calls that need to be replaced before version 4.0.0 removes these
functions; the [migration guide](../../integration/migration_guide.md#replace-deprecated-functions) describes how.
A deleted function, unlike a removed one, still takes part in overload resolution. A call that would select it
therefore fails to compile instead of silently selecting another overload. This matters for the deprecated
`from_*(ptr, len)` overloads of [`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md),
[`from_ubjson`](../basic_json/from_ubjson.md), [`from_bjdata`](../basic_json/from_bjdata.md),
[`from_bon8`](../basic_json/from_bon8.md), and [`from_bson`](../basic_json/from_bson.md): without them, a call like
`from_cbor(ptr, len)` would compile, read `ptr` as a NUL-terminated string, and convert `len` to the `strict` parameter.
The macro does not affect the deprecated legacy comparison of discarded values, which is controlled by
[`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](json_use_legacy_discarded_value_comparison.md).
## Default definition
The default value is `0` (disabled, the deprecated functions can still be called, and the compiler warns about it).
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
```
## Notes
!!! info "CMake option"
The macro can also be set with the CMake option
[`JSON_DeleteDeprecatedFunctions`](../../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
effect. Define it for the whole project to avoid different declarations of the same class in different
translation units.
!!! note "ABI compatibility"
The macro only turns calls that compile into calls that do not; it does not change the layout or the behavior of
any type. Its value is therefore not encoded in the [namespace](../../features/namespace.md).
## Examples
??? example "Example: default behavior (macro not defined)"
Without the macro, the deprecated overload is called, and the compiler warns about it:
```cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
auto j = json::from_cbor(v.data(), v.size());
// warning: 'from_cbor' is deprecated: Since 3.8.0; use from_cbor(ptr, ptr + len)
}
```
??? example "Example: deleted deprecated functions (macro defined to 1)"
With the macro, the call does not compile:
```cpp
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
const std::vector<std::uint8_t> v = {0x82, 0x01, 0x02};
auto j = json::from_cbor(v.data(), v.size());
// error: call to deleted function 'from_cbor'
}
```
## See also
- [Roadmap: removal of deprecated functions](../../community/roadmap.md#removal-of-deprecated-functions) - the
deprecated functions and the version they were deprecated in
- [Migration guide: replace deprecated functions](../../integration/migration_guide.md#replace-deprecated-functions) -
how to replace each deprecated function
## Version history
- Added in version 3.13.0.
- Planned to be removed in version 4.0.0, which removes the deprecated functions. The deprecated `from_*(ptr, len)`
overloads stay deleted in version 4.0.0.
@@ -112,4 +112,3 @@ The default value is `0` (disabled — existing behavior is preserved).
## Version history
- Added in version 3.13.0.
- Planned to become the default (with the macro removed) in version 4.0.0.
@@ -44,7 +44,7 @@ By default, implicit conversions are enabled.
## Examples
??? example "Example: implicit and explicit conversions"
??? example "Example: implicit conversion"
This is an example for an implicit conversion:
@@ -1,139 +0,0 @@
# JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS /* value */
```
When defined to `1`, maps whose keys are enums (such as `std::map<E, T>` or `std::unordered_map<E, T>`) are stored as
JSON objects, using the enum's own conversion for the keys. By default, they are stored as arrays of `[key, value]`
pairs.
## Default definition
The default value is `0` (disabled — existing behavior is preserved).
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
```
## Notes
!!! note "Background"
JSON object keys are strings, so a map is only stored as an object if its keys can be converted to a string type.
Enums are not, even if [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps them to strings, so a
map with enum keys becomes an array of `[key, value]` pairs:
```json
[["stopped", "aa"], ["completed", "bb"]]
```
With this macro, the same map becomes an object
(see [#4378](https://github.com/nlohmann/json/issues/4378)):
```json
{"completed": "bb", "stopped": "aa"}
```
!!! note "Maps with non-unique keys"
Maps that allow duplicate keys, such as `std::multimap<E, T>` or `std::unordered_multimap<E, T>`, are not affected
by the macro and are still stored as arrays of `[key, value]` pairs, as an object cannot hold duplicate keys.
!!! note "Reading"
Reading is not affected by the macro: a map with enum keys can always be read from both an array of pairs and an
object. For the latter, each key is converted to the enum with its `from_json` function, e.g., the one defined by
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md). Data written without the macro can therefore
still be read after enabling it.
!!! warning "Keys must serialize to distinct strings"
Each key is converted with the enum's `to_json` function. If a key is not converted to a string (for instance, an
enum without [`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md), which is stored as an integer, or an
enumerator mapped to `nullptr`), [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) is thrown.
If two keys are converted to the same string (for instance, because
[`NLOHMANN_JSON_SERIALIZE_ENUM`](nlohmann_json_serialize_enum.md) maps an unlisted enumerator to the first entry),
[`type_error.318`](../../home/exceptions.md#jsonexceptiontype_error318) is thrown. In both cases, the target value
is not changed.
!!! warning "Opt-in only"
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
!!! note "ABI compatibility"
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_ekmo`), 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.
## Examples
??? example "Example: default behavior (macro not defined)"
Without the macro, a map with enum keys is stored as an array of pairs:
```cpp
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is [["stopped","aa"],["completed","bb"]]
}
```
??? example "Example: objects for enum-keyed maps (macro defined to 1)"
With the macro, the same map is stored as an object:
```cpp
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 1
#include <map>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
enum TaskState { TS_STOPPED, TS_RUNNING, TS_COMPLETED };
NLOHMANN_JSON_SERIALIZE_ENUM(TaskState, {
{TS_STOPPED, "stopped"},
{TS_RUNNING, "running"},
{TS_COMPLETED, "completed"},
})
int main()
{
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// j is {"completed":"bb","stopped":"aa"}
auto m2 = j.get<std::map<TaskState, std::string>>();
// m2 == m
}
```
## See also
- [Specializing enum conversion](../../features/enum_conversion.md)
- [**NLOHMANN_JSON_SERIALIZE_ENUM**](nlohmann_json_serialize_enum.md) - serialize/deserialize an enum
- [**NLOHMANN_JSON_SERIALIZE_ENUM_STRICT**](nlohmann_json_serialize_enum_strict.md) - serialize/deserialize an enum with
exceptions
## Version history
- Added in version 3.13.0.
@@ -5,17 +5,13 @@
```
When defined on x86-64, the parser of [`basic_json_document`](../basic_json_document/index.md)
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3 without asking the CPU first.
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3, 16 bytes at a time, using the "lookup4"
algorithm of [simdjson](https://github.com/simdjson/simdjson). Without it, non-ASCII text is validated one UTF-8
sequence at a time on x86-64; on AArch64, the vector check uses NEON and is always on.
By default, the parser checks once at run time whether the CPU has SSSE3 (all x86-64 CPUs since about 2011 have it)
and then validates non-ASCII text 16 bytes at a time, using the "lookup4" algorithm of
[simdjson](https://github.com/simdjson/simdjson); on CPUs without SSSE3, it validates one UTF-8 sequence at a time.
The vector check is compiled for SSSE3 with a function attribute (GCC 4.9 and later, Clang), so this needs no compiler
option. With MSVC, the check uses `__cpuid`. On AArch64, the vector check uses NEON and is always on.
Define the macro only together with a compiler option that enables SSSE3 (e.g. `-mssse3`, or `-march=` with a CPU that
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.
SSSE3 is not part of the x86-64 baseline, so the code must be compiled for it: define the macro only together with a
compiler option that enables SSSE3 (e.g. `-mssse3`, or `-march=` with a CPU that has it), and only for programs that
run on such CPUs. The same input is accepted or rejected either way; only the speed of non-ASCII text differs.
!!! warning "Define consistently"
@@ -41,9 +41,6 @@ inline void from_json(const BasicJsonType& j, type& e);
conversion. Select this default pair carefully. See example 1 below.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -83,7 +80,6 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM_STRICT`](./nlohmann_json_serialize_enum_strict.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
@@ -44,9 +44,6 @@ inline void from_json(const BasicJsonType& j, type& e);
`"enum value out of range for <type>"`.
- If an enum or JSON value is specified in multiple conversions, the first matching conversion from the top of the
list will be returned when converting to or from JSON. See example 2 below.
- Maps with enum keys (e.g., `std::map<ENUM_TYPE, T>`) are stored as arrays of `[key, value]` pairs by default.
Define [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md) to store them as objects
with the converted keys. Such maps can be read from both forms.
## Examples
@@ -102,7 +99,6 @@ inline void from_json(const BasicJsonType& j, type& e);
- [Specializing enum conversion](../../features/enum_conversion.md)
- [`NLOHMANN_JSON_SERIALIZE_ENUM`](./nlohmann_json_serialize_enum.md)
- [`JSON_DISABLE_ENUM_SERIALIZATION`](json_disable_enum_serialization.md)
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](json_use_objects_for_enum_keyed_maps.md)
## Version history
@@ -21,18 +21,17 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
| Compiler | Architecture | Operating System | CI |
|----------------------------------------------|--------------|-----------------------------------|-----------|
| AppleClang 15.0.0.15000040; Xcode 15.0.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000100; Xcode 15.1 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000100; Xcode 15.2 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000309; Xcode 15.3 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 15.0.0.15000309; Xcode 15.4 | arm64 | macOS 14.7.2 (Sonoma) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16.1 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 16.0.0.16000026; Xcode 16.2 | arm64 | macOS 15.2 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000013; Xcode 16.3 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000013; Xcode 16.4 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000319; Xcode 26.0.1 | arm64 | macOS 15.5 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000404; Xcode 26.1.1 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000603; Xcode 26.2 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 17.0.0.17000604; Xcode 26.3 | arm64 | macOS 15.7.9 (Sequoia) | GitHub |
| AppleClang 21.0.0.21000099; Xcode 26.4.1 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| AppleClang 21.0.0.21000101; Xcode 26.5 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| AppleClang 21.0.0.21000101; Xcode 26.6 | arm64 | macOS 26.6.2 (Tahoe) | GitHub |
| Clang 3.4.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 3.5.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 3.6.2 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
@@ -90,7 +89,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
| GNU 13.3.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 14.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 15.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.2.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.1.0 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| GNU 16.1.0 | arm64 | Ubuntu 24.04 | GitHub |
| icpc (ICC) 2021.10.0 20230609 | x86_64 | Ubuntu 22.04 LTS | GitHub |
| icpx (Intel oneAPI DPC++/C++) 2025.3.2 | x86_64 | Ubuntu 24.04 LTS | GitHub |
+2 -12
View File
@@ -64,8 +64,6 @@ The following macros guard changes that are planned to become the default in ver
| [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) | `0` | `1`: reading from a stream does not consume the character after a number | – | 3.13.0 |
| [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) | `0` | `1`: a NUL byte in the input is a parse error instead of the end of input | [`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) | 3.13.0 |
| [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) | `0` | `1`: `to_cbor`, `to_ubjson`, `to_bjdata`, and `to_bson` throw for strings that are not valid UTF-8 by default | [`JSON_StrictBinaryUTF8`](../integration/cmake.md#json_strictbinaryutf8) | 3.13.0 |
| [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md) | `0` | `1`: a `basic_json` value can no longer be created from a one-element tuple of a reference to it, such as `std::forward_as_tuple(j)`| [`JSON_DisableTupleReferenceConversion`](../integration/cmake.md#json_disabletuplereferenceconversion) | 3.13.0 |
| [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) | `0` | removed: the deprecated functions are removed (see below); the `from_*(ptr, len)` overloads stay deleted | [`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) | 3.13.0 |
For example, the following makes a 3.x release behave like version 4.0 with respect to these changes:
@@ -77,8 +75,6 @@ For example, the following makes a 3.x release behave like version 4.0 with resp
#define JSON_PRECISE_STREAM_POSITION 1
#define JSON_STRICT_NUL_HANDLING 1
#define JSON_STRICT_BINARY_UTF8 1
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1
#define JSON_DELETE_DEPRECATED_FUNCTIONS 1
#include <nlohmann/json.hpp>
```
@@ -88,13 +84,8 @@ way to achieve this.
### Removal of deprecated functions
Version 4.0 will remove all deprecated functions. Compiling with deprecation warnings enabled shows which of them your
code still uses. Defining [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to
`1` turns these warnings into errors, as the deprecated functions are then deleted. The
[migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to replace each of them.
The `from_*` overloads taking a pointer and a length are not removed in version 4.0, but stay deleted. Without them, a
call like `from_cbor(ptr, len)` would still compile: it would read `ptr` as a NUL-terminated string and convert `len`
to the `strict` parameter.
code still uses. The [migration guide](../integration/migration_guide.md#replace-deprecated-functions) shows how to
replace each of them.
| Deprecated | Since | Migration |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|----------------------------------------------------------------------------------|
@@ -106,7 +97,6 @@ to the `strict` parameter.
| [`json_pointer::operator string_t`](../api/json_pointer/operator_string_t.md) | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| [`json_pointer`](../api/json_pointer/index.md) with a `basic_json` type as template argument, and the overloads of `value`, `contains`, `operator[]`, and `at` accepting such a pointer | 3.11.0 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| Comparing a [`json_pointer`](../api/json_pointer/index.md) with a string via [`operator==`](../api/json_pointer/operator_eq.md) or [`operator!=`](../api/json_pointer/operator_ne.md) | 3.11.2 | [JSON Pointers](../integration/migration_guide.md#json-pointers) |
| [`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) with `(ptr, len)` | 3.13.0 | [Parsing](../integration/migration_guide.md#parsing) |
The deprecated legacy comparison of discarded values is controlled by a macro and therefore listed in the table above.
@@ -1,52 +0,0 @@
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include <nlohmann/json_view.hpp>
using json = nlohmann::json;
using json_document = nlohmann::json_document;
using image_check = json_document::image_check;
int main()
{
std::cout << std::boolalpha;
// the image of a parsed document -- as if read back from a cache file or
// received from another process running the same build of the library
const std::string text = R"({"name": "cache", "note": "caf\u00e9", "replicas": ["db2", "db3"]})";
const json_document parsed = json_document::parse(text);
const std::vector<std::uint8_t> image = parsed.save();
// (1)/(2) load() needs no parsing, yet dumps exactly what parsing did
const json_document borrowed = json_document::load(image);
std::cout << (borrowed.root().dump() == parsed.root().dump()) << '\n';
std::cout << borrowed.owns_source() << '\n'; // borrowed: still points into `image`
// (3) load(std::move(image)) keeps the vector instead of copying it
std::vector<std::uint8_t> to_move = image;
const json_document owned = json_document::load(std::move(to_move));
std::cout << owned.owns_source() << '\n';
// a damaged image -- the last byte of the decoded string "note" holds
// (an escape sequence, so it was unescaped into the document's own
// buffer), flipped, as storage or transport corruption might do
std::vector<std::uint8_t> damaged = image;
damaged[damaged.size() - 2] = 0xFF;
// image_check::full inspects strings and numbers, so it catches the damage
try
{
static_cast<void>(json_document::load(damaged, image_check::full));
}
catch (const json::parse_error& e)
{
std::cout << e.id << '\n';
}
// image_check::bounds only checks structure and bounds, so a cache the
// process already trusts loads without the extra scan -- reading a value
// the damage did not touch is still safe
const json_document trusted = json_document::load(damaged, image_check::bounds);
std::cout << trusted.root()["name"].get<std::string>() << '\n';
}
@@ -1,5 +0,0 @@
true
false
true
116
cache
@@ -1,30 +0,0 @@
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
#include <nlohmann/json_view.hpp>
using json_document = nlohmann::json_document;
int main()
{
std::cout << std::boolalpha;
// a configuration a service parses once and then caches as an image, so
// that later requests can load() it instead of parsing the text again
const std::string text = R"({"name": "cache", "host": "db1", "port": 6379, "replicas": ["db2", "db3"]})";
const json_document config = json_document::parse(text);
// save() turns the parsed document into a byte buffer: a 64-byte header,
// the node index, the source text, and the decoded strings
const std::vector<std::uint8_t> image = config.save();
std::cout << image.size() << '\n';
// the same document always saves to the same bytes
std::cout << (image == json_document::parse(text).save()) << '\n';
// loading the image back needs no parsing, yet dumps exactly what
// parsing the text produced
const json_document reloaded = json_document::load(image);
std::cout << (reloaded.root().dump() == config.root().dump()) << '\n';
}
@@ -1,3 +0,0 @@
316
true
true
@@ -168,9 +168,9 @@ The library maps CBOR types to JSON value types as follows:
!!! warning "Negative integer overflow"
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
result to fit into `number_integer_t` (`std::int64_t` by default), the result is stored as `number_float_t`, like
a too small integer in JSON text. For example, `-18446744073709551616` (`0x3B` followed by eight `0xFF` bytes) is
stored as `-1.8446744073709552e+19`.
result to fit into `number_integer_t` (`std::int64_t` by default), parsing fails with a
[`parse_error.112`](../../home/exceptions.md#jsonexceptionparse_error112) exception rather than overflowing
silently.
!!! warning "Object keys"
@@ -58,23 +58,6 @@ assert(jPi.get<TaskState>() == TS_INVALID );
--8<-- "examples/nlohmann_json_serialize_enum.output"
```
## Maps with enum keys
By default, maps with enum keys, such as `std::map<TaskState, std::string>`, are stored as arrays of `[key, value]`
pairs, because JSON object keys must be strings. Define
[`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) before including the
library to store them as objects, with the keys converted by the enum's `to_json()` function:
```cpp
std::map<TaskState, std::string> m = {{TS_STOPPED, "aa"}, {TS_COMPLETED, "bb"}};
json j = m;
// default: [["stopped","aa"],["completed","bb"]]
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS: {"completed":"bb","stopped":"aa"}
```
Either form can be read back, with or without the macro.
## Notes
Just as in [Arbitrary Type Conversions](arbitrary_types.md) above,
+1 -2
View File
@@ -14,8 +14,7 @@ C++ types, and finally serialize it again.
[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;
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
parsed document again without parsing it.
[Editable documents](json_view.md#editing-a-document) can also be modified.
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values
-52
View File
@@ -250,57 +250,6 @@ edits. See [`basic_json_document`'s Edits](../api/basic_json_document/index.md#e
throws (the *basic* guarantee, not the strong one `dump()` and the read-only functions provide). How edits are kept in
the index is described in the [architecture overview](../home/architecture.md#node-index-of-json-views).
## Images
[`save()`](../api/basic_json_document/save.md) writes a document as an *image*: a byte buffer that
[`load()`](../api/basic_json_document/load.md) reads back into a document without parsing -- no lexing, no building
the node index, nothing but copying the nodes and pointing the text and the decoded strings at the image. Where
[`parse_copy()`](../api/basic_json_document/parse_copy.md) still has to scan the whole input,
[`load()`](../api/basic_json_document/load.md) turns that scan into a copy of the node index alone.
**Why.** A document that is parsed once and then read many times -- a configuration loaded at startup, a template
rendered on every request, a large reference dataset a worker process needs in memory -- pays for parsing once but
can amortize [`save()`](../api/basic_json_document/save.md)'s cost across every later load. That makes images useful
for a cache: save a document the first time it is parsed (to a file, a shared-memory segment, an in-process cache),
and [`load()`](../api/basic_json_document/load.md) it on every later use instead of parsing the source text again.
They are just as useful for handing a parsed document to another process (or a forked worker) running the same build
of the library, since [`load()`](../api/basic_json_document/load.md) turns the transfer into a copy of the node index
plus pointers into the received bytes, not a re-parse.
**Choosing a check.** [`load()`](../api/basic_json_document/load.md) takes an
[`image_check`](../api/basic_json_document/load.md#image_check) that trades validation against speed:
`image_check::full` (the default) checks everything the parser itself guarantees, so a checked image is exactly as
safe to read and serialize as a freshly parsed document -- the right choice whenever the image did not come straight
from this process's own [`save()`](../api/basic_json_document/save.md), such as a file or a network peer.
`image_check::bounds` only checks structure and bounds -- cheaper, since it skips scanning the text and the decoded
strings -- and fits a cache the process trusts, one it wrote and reads back itself. `image_check::none` skips
validation entirely, for an image trusted as much as the process's own memory. See
[`load()`'s Notes](../api/basic_json_document/load.md#notes) for exactly what each level does and does not guarantee.
??? example "Example: cache a parsed configuration as an image"
```cpp
--8<-- "examples/basic_json_document__save.cpp"
```
Output:
```json
--8<-- "examples/basic_json_document__save.output"
```
!!! warning "Experimental"
The image format is versioned but not yet stable, and may change in an incompatible way before it is declared
stable. It is little-endian only, and tied to the library build that wrote it -- use it to cache a document or to
hand one to another process running the *same* build, not as a long-term storage format; keep the original JSON
text if a saved document needs to be readable by a future library version.
The idea of a document you can read without parsing comes from zero-copy formats such as
[FlatBuffers](https://github.com/google/flatbuffers) and [YaFF](https://github.com/yandex/yaff); the check
[`load()`](../api/basic_json_document/load.md) runs follows the idea of FlatBuffers' Verifier. No code is taken from
either.
## Choosing between `json`, `ordered_json`, the SAX interface, and `json_view`
| | [`json`](../api/json.md) / [`ordered_json`](../api/ordered_json.md) | [SAX interface](parsing/sax_interface.md) | [`json_document`](../api/json_document.md) / [`json_view`](../api/json_view.md) | [`json_editable_document`](../api/json_editable_document.md) / [`json_editable_view`](../api/json_editable_view.md) |
@@ -309,7 +258,6 @@ either.
| **Mutability** | freely mutable | not applicable (a one-shot event stream) | read-only | [`set`](../api/basic_json_document/set.md)/[`push_back`](../api/basic_json_document/push_back.md)/[`insert`](../api/basic_json_document/insert.md)/[`erase`](../api/basic_json_document/erase.md) edit in place; the source text is never rewritten |
| **What you get** | a full tree you can read, write, and keep as long as you like | a sequence of callbacks; whatever your handler builds from them | a flat index plus, on demand, [`materialize()`](../api/basic_json_view/materialize.md)d `json`/`ordered_json` values for the parts you actually use | the same, plus [`dump()`](../api/basic_json_view/dump.md) of an edited document that keeps the member order and, with [`number_format::source`](../api/basic_json_view/number_format.md), the spelling of every untouched number |
| **Typical use** | general-purpose JSON handling: config, request/response bodies you build or modify, anything you hold onto | validating or projecting a text into your own data structure without ever holding the whole thing as JSON | large or high-volume input where you only need part of it, or need it repeatedly, and can keep the source text (or a copy) alive for as long as the document lives | a document you read, patch a few fields of, and write back -- a configuration file, for instance -- where the rest of it should come back exactly as it was |
| **Caching/reload** | not applicable -- re-parse, or roll your own serialization | not applicable | [`save()`](../api/basic_json_document/save.md)/[`load()`](../api/basic_json_document/load.md): cache the parsed index as an image and reload it without parsing | same, saving the document's current -- possibly edited -- state |
## Version history
+1 -20
View File
@@ -23,17 +23,6 @@ This macro overrides [`#!cpp catch`](https://en.cppreference.com/w/cpp/language/
See [full documentation of `JSON_CATCH_USER(exception)`](../api/macros/json_throw_user.md).
## `JSON_DELETE_DEPRECATED_FUNCTIONS`
When defined to `1`, all deprecated functions are declared as deleted instead of only being marked as deprecated, so
code that still calls them no longer compiles. This way, you can find all calls that need to be replaced before version
4.0.0 removes these functions.
The macro can also be set with the CMake option
[`JSON_DeleteDeprecatedFunctions`](../integration/cmake.md#json_deletedeprecatedfunctions) (`OFF` by default).
See [full documentation of `JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md).
## `JSON_DIAGNOSTICS`
This macro enables extended diagnostics for exception messages. Possible values are `1` to enable or `0` to disable
@@ -97,8 +86,7 @@ See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json
## `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
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. This
is planned to become the default in version 4.0.0.
value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise.
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
@@ -210,13 +198,6 @@ default.
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
## `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`
When defined to `1`, maps with enum keys (e.g., `std::map<E, T>`) are stored as objects, using the enum's conversion for
the keys, instead of arrays of `[key, value]` pairs. It is switched off (`0`) by default.
See [full documentation of `JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md).
## `JSON_USE_SIMDUTF`
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
-2
View File
@@ -21,8 +21,6 @@ The complete default namespace name is derived as follows:
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
- [`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
appends `_ekmo`.
- 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)
below.
+1 -8
View File
@@ -55,7 +55,7 @@ always use the scalar path regardless of this macro.
Parsing always produces SAX events internally; [`parse`](../api/basic_json/parse.md) simply feeds them to a consumer
that builds a complete `basic_json` value tree (a DOM) in memory. For documents too large to comfortably hold as
a DOM, three alternatives avoid building it:
a DOM, two alternatives avoid building it:
- Implement the [SAX interface](parsing/sax_interface.md) directly and pass it to
[`sax_parse`](../api/basic_json/sax_parse.md); only the parts of the input you choose to keep ever become
@@ -64,12 +64,6 @@ a DOM, three alternatives avoid building it:
discard finished elements as soon as they are handled, so memory usage stays bounded by one element (plus the
unparsed remainder of the input) instead of the whole document -- see the [recipe for streaming a large homogeneous
array](parsing/parser_callbacks.md#recipe-streaming-a-large-homogeneous-array).
- Parse into a [`json_document`](json_view.md) (`#!cpp <nlohmann/json_view.hpp>`) instead of a `basic_json`. It keeps
the input text and builds a flat index of 16 bytes per value; strings and numbers are not copied, but read from the
text when needed. Read-only [views](../api/basic_json_view/index.md) give the familiar element access, and only the
parts you [`materialize()`](../api/basic_json_view/materialize.md) become `basic_json` values. A document that
borrows the text instead of owning a copy needs the text to outlive it; see
[choosing between `json`, the SAX interface, and `json_view`](json_view.md#choosing-between-json-ordered_json-the-sax-interface-and-json_view).
If the data is naturally record-oriented, consider [JSON Lines](parsing/json_lines.md) instead of one large JSON
document: reading and parsing it line by line with `#!cpp std::getline` means only one line's value is ever in memory
@@ -215,7 +209,6 @@ those headers are then never processed by the compiler at all.
- [Architecture](../home/architecture.md) - how input adapters, the lexer, and the serializer fit together
- [Parsing](parsing/index.md) - the available parsing functions and inputs
- [SAX interface](parsing/sax_interface.md) - parse without building a DOM
- [Zero-copy JSON views](json_view.md) - parse into a flat index of the text and read it without building a DOM
- [Binary formats](binary_formats/index.md) - compact alternatives to JSON text
- [Object Order](object_order.md) - `json` vs. `ordered_json` and other `ObjectType` choices
- [Template Parameter Requirements](types/template_parameters.md) - custom container and allocator types
@@ -134,10 +134,9 @@ That is, `-0` is stored as a signed integer, but the serialization does not repr
### Number serialization
- Integer numbers are serialized as is; that is, no scientific notation is used.
- Floating-point numbers are serialized with the fewest digits that read back as the same value (the closest such
digits if there are several), in the layout of the `#!c %g` printf modifier: `#!c 1.5`, `#!c 100.0`, `#!c 1e+100`.
Doubles are converted with the algorithm of [Żmij](https://github.com/vitaut/zmij), floats with Grisu2, which
can write more digits than necessary.
- Floating-point numbers are serialized as specified by the `#!c %g` printf modifier with
[`std::numeric_limits<double>::max_digits10`](https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10)
significant digits. The rationale is to use the shortest representation while still allowing round-tripping.
!!! hint "Notes regarding precision of floating-point numbers"
@@ -545,10 +545,9 @@ therefore silently changes parse results rather than raising an error. See
specifiers, for which the library likewise provides only `#!cpp double` and `#!cpp long double` overloads
(`#!cpp float` is promoted to `#!cpp double`).
If `#!cpp std::numeric_limits<NumberFloatType>` describes an IEEE 754 binary64 number, `dump` uses the algorithm of
Żmij, which produces the shortest representation that round-trips. For IEEE 754 binary32 numbers, it uses Grisu2,
which produces a short representation that round-trips. Otherwise the `snprintf` fallback with `max_digits10` digits is
used.
If `#!cpp std::numeric_limits<NumberFloatType>` describes an IEEE 754 binary32 or binary64 number, `dump` uses the
Grisu2 algorithm, which produces the shortest representation that round-trips. Otherwise the `snprintf` fallback with
`max_digits10` digits is used.
### Required for the binary formats
@@ -560,7 +559,7 @@ binary32 or binary64 field and have no encoding for `#!cpp long double`.
| Type | Support |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------|
| `#!cpp double` (default) | full; shortest round-trip output through Żmij |
| `#!cpp double` (default) | full; short round-trip output through Grisu2 |
| `#!cpp float` | full; short round-trip output through Grisu2 |
| `#!cpp long double` | `dump` and `parse` only; the binary format writers do not compile, as they only handle IEEE 754 binary32 and binary64 |
| any other type | not usable |
-8
View File
@@ -259,14 +259,6 @@ edited:
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
(`navigation<Editable>`).
Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_json_document/load.md),
[`detail/view/image.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/image.hpp)) store
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
## Input adapters
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
+6 -80
View File
@@ -331,6 +331,9 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON binary: byte array length cannot be negative, is -1
```
```
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
```
```
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
```
@@ -388,23 +391,6 @@ A UBJSON high-precision number could not be parsed.
[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A
```
### json.exception.parse_error.116
[`basic_json_document::load()`](../api/basic_json_document/load.md) rejected an
[image](../features/json_view.md#images): either the bytes are not one [`save()`](../api/basic_json_document/save.md)
could have written (too short, an unknown magic number or format version, or sizes that do not fit the buffer), or
they are, but fail the requested [`image_check`](../api/basic_json_document/load.md#image_check).
!!! failure "Example message"
```
[json.exception.parse_error.116] parse error: invalid json_document image: the check failed
```
!!! note
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
## Iterator errors
This exception is thrown if iterators passed to a library function do not match
@@ -613,9 +599,6 @@ During implicit or explicit value conversion, the JSON type must be compatible w
[json.exception.type_error.302] type must be string, but is object
```
This exception is also thrown with [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)
if a key of a map with enum keys is not converted to a string, for instance, because the enum is stored as an integer.
### json.exception.type_error.303
To retrieve a reference to a value stored in a `basic_json` object with `get_ref`, the type of the reference must match the value type. For instance, for a JSON array, the `ReferenceType` must be `array_t &`.
@@ -808,19 +791,6 @@ The dynamic type of the object cannot be represented in the requested serializat
Encapsulate the JSON value in an object. That is, instead of serializing `#!json true`, serialize `#!json {"value": true}`
### json.exception.type_error.318
With [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md), a map with enum
keys is stored as an object. This exception is thrown if two of its keys are converted to the same string, so one of the
entries would be lost. This happens, for instance, if [`NLOHMANN_JSON_SERIALIZE_ENUM`](../api/macros/nlohmann_json_serialize_enum.md)
does not list an enumerator and it is therefore converted like the first listed one.
!!! failure "Example message"
```
[json.exception.type_error.318] duplicate object key 'red'
```
### json.exception.type_error.319
[`basic_json_document::set`](../api/basic_json_document/set.md) and
@@ -839,40 +809,6 @@ from JSON text.
This exception was added in version 3.13.0, together with editable [`json_document`s](../features/json_view.md).
### json.exception.type_error.320
[`basic_json_document::save()`](../api/basic_json_document/save.md) cannot write an
[image](../features/json_view.md#images) of a [discarded](../api/basic_json_document/is_discarded.md) document.
[`save()`](../api/basic_json_document/save.md) and [`load()`](../api/basic_json_document/load.md) also throw this
exception on a big-endian target, since the image format is little-endian only.
!!! failure "Example messages"
```
[json.exception.type_error.320] cannot save a discarded json_document
```
```
[json.exception.type_error.320] json_document images need a little-endian target
```
!!! note
This exception was added in version 3.13.0, together with [images](../features/json_view.md#images).
### json.exception.type_error.321
A discarded value (one created by [`parse()`](../api/basic_json/parse.md) with a callback that returns `false` for the
value, or by default-constructing a [`basic_json`](../api/basic_json/index.md) with
[`value_t::discarded`](../api/basic_json/value_t.md)) was passed to a binary serialization function, either directly or
nested in an array or object. There is no way to represent a discarded value in CBOR, MessagePack, UBJSON, BJData, or BSON.
!!! failure "Example message"
Serializing `#!json [1, 2]` to CBOR, where the second element was discarded by a parser callback:
```
[json.exception.type_error.321] cannot serialize discarded value to CBOR
```
## Out of range
This exception is thrown in case a library function is called on an input parameter that exceeds the expected range, for instance, in the case of array indices or nonexisting object keys.
@@ -945,18 +881,13 @@ The JSON Patch operations 'remove' and 'add' cannot be applied to the root eleme
### json.exception.out_of_range.406
A parsed number could not be stored without changing it to NaN or INF. For the binary formats, this happens when a
finite floating-point number does not fit into [`number_float_t`](../api/basic_json/number_float_t.md), for example a
double-precision number when `number_float_t` is `#!cpp float`.
A parsed number could not be stored as without changing it to NaN or INF.
!!! failure "Example messages"
!!! failure "Example message"
```
number overflow parsing '10E1000'
```
```
[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow
```
### json.exception.out_of_range.407
@@ -1105,9 +1036,7 @@ MessagePack's ext type and BSON's binary subtype are each stored in a single byt
so they do not support an input of 4 GiB or more. The same 32-bit limit applies to an **editable** document's own
storage: [`set`](../api/basic_json_document/set.md) and [`push_back`](../api/basic_json_document/push_back.md) throw
this exception once the strings and number tokens written by edits reach 4 GiB in total, or once more than
4294967295 arrays/objects have had an element set or appended to them. The same limit applies to an
[image](../features/json_view.md#images): [`save()`](../api/basic_json_document/save.md) throws it if the node
count, the text, or the decoded strings it would write would individually reach 4 GiB.
4294967295 arrays/objects have had an element set or appended to them.
!!! failure "Example messages"
@@ -1117,9 +1046,6 @@ count, the text, or the decoded strings it would write would individually reach
```
[json.exception.out_of_range.416] edits of 4 GiB or more are not supported by json_document
```
```
[json.exception.out_of_range.416] images of 4 GiB or more are not supported by json_document
```
!!! note
-2
View File
@@ -18,8 +18,6 @@ The class contains the UTF-8 Decoder from Bjoern Hoehrmann which is licensed und
The class contains a slightly modified version of the Grisu2 algorithm from Florian Loitsch which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2009 [Florian Loitsch](https://florian.loitsch.com/)
The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright &copy; 2025 [Victor Zverovich](https://github.com/vitaut)
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright &copy; 2021 The fast_float authors
-6
View File
@@ -132,12 +132,6 @@ Build the unit tests when [`BUILD_TESTING`](https://cmake.org/cmake/help/latest/
Enable CI build targets. The exact targets are used during the several CI steps and are subject to change without notice. This option is `OFF` by default.
### `JSON_DeleteDeprecatedFunctions`
Delete the deprecated functions instead of only deprecating them by defining the macro
[`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md). This option is `OFF` by
default.
### `JSON_Diagnostics`
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
@@ -14,13 +14,6 @@ deprecations are annotated with
[`HEDLEY_DEPRECATED_FOR`](https://nemequ.github.io/hedley/api-reference.html#HEDLEY_DEPRECATED_FOR) to report which
function to use instead.
!!! tip "Find all calls of deprecated functions"
Define [`JSON_DELETE_DEPRECATED_FUNCTIONS`](../api/macros/json_delete_deprecated_functions.md) to `1` (or set the
CMake option [`JSON_DeleteDeprecatedFunctions`](cmake.md#json_deletedeprecatedfunctions)) to delete all deprecated
functions. Every remaining call then fails to compile, even if deprecation warnings are disabled, so your code is
ready for version 4.0.0 once it compiles with the macro.
### Parsing
- Function `friend std::istream& operator<<(basic_json&, std::istream&)` is deprecated since 3.0.0. Please use
@@ -48,10 +41,8 @@ function to use instead.
[`from_ubjson`](../api/basic_json/from_ubjson.md), and [`from_bson`](../api/basic_json/from_bson.md)) via initializer
lists is deprecated since 3.8.0. Instead, pass two iterators; for instance, call `from_cbor(ptr, ptr+len)` instead of
`from_cbor({ptr, len})`. Likewise, passing a pointer and a length as two separate arguments to `from_cbor`,
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0, and to
[`from_bjdata`](../api/basic_json/from_bjdata.md) and [`from_bon8`](../api/basic_json/from_bon8.md) since 3.13.0; call
`from_cbor(ptr, ptr+len)` instead of `from_cbor(ptr, len)`. These overloads will not be removed in version 4.0.0, but
deleted, so a call like `from_cbor(ptr, len)` cannot compile and convert `len` to the `strict` parameter.
`from_msgpack`, `from_ubjson`, and `from_bson` is deprecated since 3.8.0; call `from_cbor(ptr, ptr+len)` instead of
`from_cbor(ptr, len)`.
=== "Deprecated"
-4
View File
@@ -240,7 +240,6 @@ nav:
- 'erase': api/basic_json_document/erase.md
- 'insert': api/basic_json_document/insert.md
- 'is_discarded': api/basic_json_document/is_discarded.md
- 'load': api/basic_json_document/load.md
- 'memory_usage': api/basic_json_document/memory_usage.md
- 'node_count': api/basic_json_document/node_count.md
- 'owns_source': api/basic_json_document/owns_source.md
@@ -249,7 +248,6 @@ nav:
- 'push_back': api/basic_json_document/push_back.md
- 'read': api/basic_json_document/read.md
- 'root': api/basic_json_document/root.md
- 'save': api/basic_json_document/save.md
- 'set': api/basic_json_document/set.md
- 'shrink_to_fit': api/basic_json_document/shrink_to_fit.md
- 'source': api/basic_json_document/source.md
@@ -366,7 +364,6 @@ nav:
- 'JSON_BRACE_INIT_COPY_SEMANTICS': api/macros/json_brace_init_copy_semantics.md
- 'JSON_CATCH_USER, JSON_THROW_USER, JSON_TRY_USER': api/macros/json_throw_user.md
- 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
@@ -388,7 +385,6 @@ nav:
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
- 'JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS': api/macros/json_use_objects_for_enum_keyed_maps.md
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
- 'JSON_VIEW_NO_SIMD': api/macros/json_view_no_simd.md
- 'JSON_VIEW_USE_SSSE3': api/macros/json_view_use_ssse3.md
+4 -15
View File
@@ -50,10 +50,6 @@
#define JSON_STRICT_BINARY_UTF8 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -96,20 +92,14 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// 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(a, b, c, d, e, f, g, h) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -119,8 +109,7 @@
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+3 -4
View File
@@ -86,9 +86,8 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
}
/// eight bytes as a little-endian word (compilers fold this into one load on
/// little-endian targets; always inlined, as GCC otherwise calls it in the
/// number loops)
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
/// little-endian targets)
inline std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
{
return static_cast<std::uint64_t>(b[0]) | (static_cast<std::uint64_t>(b[1]) << 8u)
| (static_cast<std::uint64_t>(b[2]) << 16u) | (static_cast<std::uint64_t>(b[3]) << 24u)
@@ -97,7 +96,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b)
}
/// eight bytes as a little-endian word
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const char* p) noexcept
inline std::uint64_t read_eight_bytes(const char* p) noexcept
{
return read_eight_bytes(reinterpret_cast<const unsigned char*>(p)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
@@ -172,7 +172,7 @@ inline void from_json(const BasicJsonType& j, EnumType& e)
typename BasicJsonType::number_unsigned_t, underlying_type>::type;
value_type val;
get_arithmetic_value(j, val);
e = static_cast<EnumType>(bool_aware_static_cast<underlying_type>(val));
e = static_cast<EnumType>(static_cast<underlying_type>(val));
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -530,40 +530,11 @@ void from_json_pair_array_to_map(const BasicJsonType& j, MapType& m)
}
}
// read a map with enum keys from an object, using the enum's own from_json for
// the keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); this is the form written
// with JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& j, Map& m, std::true_type /*key is enum*/)
{
if (!j.is_object())
{
return false;
}
m.clear();
for (const auto& p : *j.template get_ptr<const typename BasicJsonType::object_t*>())
{
m.emplace(BasicJsonType(p.first).template get<typename Map::key_type>(), p.second.template get<typename Map::mapped_type>());
}
return true;
}
template<typename BasicJsonType, typename Map>
inline bool from_json_enum_keyed_object(const BasicJsonType& /*j*/, Map& /*m*/, std::false_type /*key is enum*/)
{
return false;
}
template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,
typename = enable_if_t < !std::is_constructible <
typename BasicJsonType::string_t, Key >::value >>
void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
from_json_pair_array_to_map(j, m);
}
@@ -572,11 +543,6 @@ template < typename BasicJsonType, typename Key, typename Value, typename Hash,
typename BasicJsonType::string_t, Key >::value >>
void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)
{
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
if (from_json_enum_keyed_object(j, m, std::is_enum<Key> {}))
{
return;
}
from_json_pair_array_to_map(j, m);
}
+23 -472
View File
@@ -11,32 +11,11 @@
#include <array> // array
#include <cmath> // signbit, isfinite
#include <cstddef> // size_t
#include <cstdint> // intN_t, uintN_t
#include <cstring> // memcpy, memmove
#include <limits> // numeric_limits
#include <type_traits> // conditional
#ifdef _MSC_VER
#include <cstdlib> // _byteswap_uint64
#endif
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
// digits of a double at once
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
#include <emmintrin.h>
#define JSON_DTOA_SSE2 1
#define JSON_DTOA_NEON 0
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
#include <arm_neon.h>
#define JSON_DTOA_SSE2 0
#define JSON_DTOA_NEON 1
#else
#define JSON_DTOA_SSE2 0
#define JSON_DTOA_NEON 0
#endif
#include <nlohmann/detail/conversions/zmij.hpp>
#include <nlohmann/detail/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
@@ -939,88 +918,6 @@ void grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)
grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
}
/*!
@brief the shortest digits of a positive finite float (other than double): Grisu2
*/
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1)
void shortest_digits(char* buf, int& len, int& decimal_exponent, FloatType value)
{
grisu2(buf, len, decimal_exponent, value);
}
/*!
@brief the shortest digits of a positive finite double: the conversion of
Zmij (see zmij.hpp), which always finds the shortest digits that read back as
the same value (Grisu2 does not for about one double in a thousand), and the
closest of them if there are several
v = buf * 10^decimal_exponent, as for grisu2()
*/
JSON_HEDLEY_NON_NULL(1)
inline void shortest_digits(char* buf, int& len, int& decimal_exponent, double value)
{
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles");
JSON_ASSERT(std::isfinite(value));
JSON_ASSERT(value > 0);
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
zmij::decimal d = zmij::to_decimal(bits);
// without trailing zeros (up to 16): 8, 4, 2, 1 at a time
while (d.significand % 100000000 == 0)
{
d.significand /= 100000000;
d.exponent += 8;
}
if (d.significand % 10000 == 0)
{
d.significand /= 10000;
d.exponent += 4;
}
if (d.significand % 100 == 0)
{
d.significand /= 100;
d.exponent += 2;
}
if (d.significand % 10 == 0)
{
d.significand /= 10;
d.exponent += 1;
}
// at most 17 digits, written from the back two at a time
static constexpr const char* pairs =
"00010203040506070809101112131415161718192021222324252627282930313233343536373839"
"40414243444546474849505152535455565758596061626364656667686970717273747576777879"
"8081828384858687888990919293949596979899";
std::array<char, 20> digits{};
std::size_t n = digits.size();
while (d.significand >= 100)
{
const std::uint64_t two_digits = d.significand % 100; // a variable: GCC calls a cast of the remainder useless where std::uint64_t is std::size_t
const auto i = static_cast<std::size_t>(two_digits) * 2;
d.significand /= 100;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
if (d.significand >= 10)
{
const auto i = static_cast<std::size_t>(d.significand) * 2;
n -= 2;
digits[n] = pairs[i];
digits[n + 1] = pairs[i + 1];
}
else
{
digits[--n] = static_cast<char>('0' + d.significand);
}
len = static_cast<int>(digits.size() - n);
std::memcpy(buf, digits.data() + n, static_cast<std::size_t>(len));
decimal_exponent = d.exponent;
}
/*!
@brief appends a decimal representation of e to buf
@return a pointer to the element following the exponent.
@@ -1150,374 +1047,6 @@ inline char* format_buffer(char* buf, int len, int decimal_exponent,
return append_exponent(buf, n - 1);
}
/// eight decimal digits (a value below 10^8) as bytes 0..9, the first digit
/// in the most significant byte: three steps that divide all lanes at once
/// by a multiplication (the conversion of Xiang JunBo, as in Zmij)
inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
{
const std::uint64_t abcd_efgh = abcdefgh + (((std::uint64_t{1} << 32u) - 10000u) * ((abcdefgh * (((std::uint64_t{1} << 40u) / 10000u) + 1u)) >> 40u));
const std::uint64_t ab_cd_ef_gh = abcd_efgh + (((std::uint64_t{1} << 16u) - 100u) * (((abcd_efgh * (((std::uint64_t{1} << 19u) / 100u) + 1u)) >> 19u) & 0x7F0000007Fu));
return ab_cd_ef_gh + (((std::uint64_t{1} << 8u) - 10u) * (((ab_cd_ef_gh * (((std::uint64_t{1} << 10u) / 10u) + 1u)) >> 10u) & 0x000F000F000F000Fu));
}
/// store the bytes of v, the most significant one first (one byte swap and
/// one store where the byte order is known: compilers do not reliably merge
/// the byte stores once this is inlined)
inline void store_msb_first(char* p, std::uint64_t v) noexcept
{
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
v = __builtin_bswap64(v);
std::memcpy(p, &v, sizeof(v));
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
std::memcpy(p, &v, sizeof(v));
#elif defined(_MSC_VER) // (little-endian on all its targets)
v = _byteswap_uint64(v);
std::memcpy(p, &v, sizeof(v));
#else
for (unsigned i = 0; i < 8; ++i)
{
p[i] = static_cast<char>(v >> (56u - (8u * i)));
}
#endif
}
/*!
@brief digits * 10^exp for a double, in the layout of format_buffer()
The layout is that of format_buffer() with min_exp -4 and max_exp 15 (the
digits10 of double). The digits are converted eight at a time and placed
with fixed-size moves instead of per-digit loops and moves of the buffer.
@param[in] digits the digits (not 0, at most 17 digits; trailing zeros allowed)
@param[in] exp the decimal exponent of the last digit
@return a pointer past the text; up to 41 bytes at @a first are written
(some beyond the returned end)
*/
JSON_HEDLEY_NON_NULL(1)
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_decimal(char* first, std::uint64_t digits, int exp) noexcept
{
JSON_ASSERT(digits != 0 && digits < 100000000000000000u);
const std::uint64_t upper = digits / 100000000u;
const std::uint64_t b0 = upper / 100000000u; // (one digit: it is its own byte)
const std::uint64_t b1 = eight_digit_bytes(upper % 100000000u);
const std::uint64_t b2 = eight_digit_bytes(digits % 100000000u);
// leading and trailing zero digits: zero bytes, counted without division
int leading = 16;
int zeros = 16;
if (b0 != 0)
{
leading = count_leading_zeros(b0) / 8;
}
else if (b1 != 0)
{
leading = 8 + (count_leading_zeros(b1) / 8);
}
else
{
leading += count_leading_zeros(b2) / 8;
}
if (b2 != 0)
{
zeros = count_trailing_zeros(b2) / 8;
}
else if (b1 != 0)
{
zeros = 8 + (count_trailing_zeros(b1) / 8);
}
// (else: 16, b0 is the one digit that is not 0)
// the digits as text at text + leading, then '0's, so that fixed-size
// moves need not check how many digits there are
std::array<char, 64> text; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
store_msb_first(text.data(), b0 + 0x3030303030303030u);
store_msb_first(text.data() + 8, b1 + 0x3030303030303030u);
store_msb_first(text.data() + 16, b2 + 0x3030303030303030u);
std::memset(text.data() + 24, '0', 40);
const int k = 24 - leading - zeros; // significant digits
const int n = k + exp + zeros; // position of the decimal point after the first digit
const char* const s0 = text.data() + leading;
if (-4 < n && n <= 15)
{
// "0.[000]digits" (n <= 0) is the digits after 1 - n leading '0's
// with the point after the first; "digits[000].0" (n >= k) and
// "dig.its" put the point after n characters
const int pad = n <= 0 ? 1 - n : 0;
const char* const s = s0 - pad;
const int len = k + pad;
const int point = n + pad;
std::memcpy(first, s, 16);
std::memcpy(first + point + 1, s + point, 24);
first[point] = '.';
return first + (point >= len ? point + 2 : len + 1);
}
// d.igitse+XX, with at least two exponent digits (as append_exponent())
std::memcpy(first, s0, 16);
std::memcpy(first + 2, s0 + 1, 16);
first[1] = '.';
char* const end = first + (k == 1 ? 1 : k + 1);
const int e = n - 1;
const auto ea = static_cast<unsigned>(e < 0 ? -e : e);
const bool three = ea >= 100;
end[0] = 'e';
end[1] = e < 0 ? '-' : '+';
end[2] = static_cast<char>('0' + (three ? ea / 100 : (ea / 10) % 10));
end[3] = static_cast<char>('0' + (three ? (ea / 10) % 10 : ea % 10));
end[4] = static_cast<char>('0' + (ea % 10));
return end + (three ? 5 : 4);
}
/*!
@brief the shortest decimal of a positive double (Zmij), as write_decimal()
writes it
For a normal double, the shorter candidate has 15 or 16 digits: they are
converted at once (two halves of eight digits) and followed by the digit
after them, if there is one, without the multiplication and division by 10
that counting the digits of one number would take. The fixed layouts move
the digits after the point by one byte.
@return a pointer past the text; up to 41 bytes at @a first are written
(some beyond the returned end)
*/
JSON_HEDLEY_NON_NULL(1)
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcept
{
const std::uint64_t sig = d.integral;
if (JSON_HEDLEY_UNLIKELY(sig < 100000000000000u || sig >= 10000000000000000u))
{
// (subnormals)
return d.has_digit ? write_decimal(first, (sig * 10) + d.digit, d.exponent) : write_decimal(first, sig, d.exponent + 1);
}
const bool sixteen = sig >= 1000000000000000u; // (else 15 digits)
const int last = d.has_digit ? d.digit : 0;
const std::uint64_t upper = sig / 100000000u;
#if JSON_DTOA_SSE2
// NOLINTBEGIN(portability-simd-intrinsics)
// the two halves in the 64-bit lanes, each as abcd * 2^32 + efgh, then as
// bytes (as eight_digit_bytes(), one lane each)
const __m128i x = _mm_set_epi64x(static_cast<long long>(sig - (upper * 100000000u)), static_cast<long long>(upper));
const __m128i abcd = _mm_srli_epi64(_mm_mul_epu32(x, _mm_set1_epi64x(109951163)), 40); // 2^40 / 10000 + 1
const __m128i abcd_efgh = _mm_add_epi64(x, _mm_mul_epu32(abcd, _mm_set1_epi64x(4294957296))); // 2^32 - 10000
// 32-bit lanes in the order of the text: abcd, efgh of both halves
const __m128i fours = _mm_shuffle_epi32(abcd_efgh, _MM_SHUFFLE(2, 3, 0, 1));
const __m128i ab = _mm_srli_epi16(_mm_mulhi_epu16(fours, _mm_set1_epi32(5243)), 3);
const __m128i ab_cd = _mm_or_si128(_mm_slli_epi32(_mm_sub_epi16(fours, _mm_mullo_epi16(ab, _mm_set1_epi32(100))), 16), ab);
// 16-bit lanes ab (< 100) -> bytes a, b: 256 * ab - 2559 * (ab / 10)
const __m128i bytes = _mm_sub_epi16(_mm_slli_epi16(ab_cd, 8), _mm_mullo_epi16(_mm_set1_epi16(2559), _mm_mulhi_epu16(ab_cd, _mm_set1_epi16(6554))));
// the last digit that is not 0 (sig is not 0)
const auto nonzero = static_cast<std::uint64_t>(_mm_movemask_epi8(_mm_cmpgt_epi8(bytes, _mm_setzero_si128())));
const int digits = 63 - count_leading_zeros(nonzero) + (sixteen ? 1 : 0); // without trailing zeros
const __m128i chars = _mm_add_epi8(bytes, _mm_set1_epi8('0'));
// the 16 characters from the first digit
const __m128i s = sixteen ? chars : _mm_or_si128(_mm_srli_si128(chars, 1), _mm_slli_si128(_mm_cvtsi32_si128('0' + last), 15));
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
const auto store_16 = [&s](char* p) noexcept
{
std::memcpy(p, &s, 16);
};
const char first_digit = static_cast<char>(_mm_cvtsi128_si32(s));
// NOLINTEND(portability-simd-intrinsics)
#elif JSON_DTOA_NEON
// as with SSE2: the halves in 32-bit lanes, then abcd, efgh of both
const uint32x2_t halves = vcreate_u32(upper | ((sig - (upper * 100000000u)) << 32u));
const uint32x2_t abcd = vmovn_u64(vshrq_n_u64(vmull_n_u32(halves, static_cast<std::uint32_t>(((std::uint64_t{1} << 40u) / 10000u) + 1u)), 40));
const uint32x2_t efgh = vmls_n_u32(halves, abcd, 10000u);
const uint32x4_t fours = vcombine_u32(vzip1_u32(abcd, efgh), vzip2_u32(abcd, efgh));
const uint32x4_t ab = vshrq_n_u32(vmulq_n_u32(fours, 5243u), 19);
const uint16x8_t ab_cd = vreinterpretq_u16_u32(vorrq_u32(ab, vshlq_n_u32(vmlsq_n_u32(fours, ab, 100u), 16)));
const uint16x8_t tens = vshrq_n_u16(vmulq_n_u16(ab_cd, 103u), 10);
const uint8x16_t bytes = vreinterpretq_u8_u16(vorrq_u16(tens, vshlq_n_u16(vmlsq_n_u16(ab_cd, tens, 10u), 8)));
// the last digit that is not 0 (sig is not 0): a nibble per byte
const std::uint64_t nonzero = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(bytes, bytes)), 4)), 0);
const int digits = ((63 - count_leading_zeros(nonzero)) / 4) + (sixteen ? 1 : 0); // without trailing zeros
const uint8x16_t chars = vaddq_u8(bytes, vdupq_n_u8('0'));
// the 16 characters from the first digit
const uint8x16_t s = sixteen ? chars : vextq_u8(chars, vdupq_n_u8(static_cast<std::uint8_t>('0' + last)), 1);
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
const auto store_16 = [&s](char* p) noexcept
{
vst1q_u8(reinterpret_cast<std::uint8_t*>(p), s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
};
const auto first_digit = static_cast<char>(vgetq_lane_u8(s, 0));
#else
const std::uint64_t hi = eight_digit_bytes(upper);
const std::uint64_t lo = eight_digit_bytes(sig - (upper * 100000000u));
// trailing zero digits: zero bytes (sig is not 0)
const int zeros = lo != 0 ? count_trailing_zeros(lo) / 8 : 8 + (count_trailing_zeros(hi) / 8);
const int digits = 15 - zeros + (sixteen ? 1 : 0); // without trailing zeros
// the 16 characters from the first digit
const std::uint64_t s_hi = (sixteen ? hi : (hi << 8u) | (lo >> 56u)) + 0x3030303030303030u;
const std::uint64_t s_lo = (sixteen ? lo : (lo << 8u) | static_cast<std::uint64_t>(last)) + 0x3030303030303030u;
const char s16 = static_cast<char>(sixteen ? '0' + last : '0'); // the 17th
const auto store_16 = [s_hi, s_lo](char* p) noexcept
{
store_msb_first(p, s_hi);
store_msb_first(p + 8, s_lo);
};
const auto first_digit = static_cast<char>(s_hi >> 56u);
#endif
const int len = d.has_digit ? 16 + (sixteen ? 1 : 0) : digits; // significant digits
const int n = 16 + (sixteen ? 1 : 0) + d.exponent; // digits before the point
if (JSON_HEDLEY_LIKELY(n >= 1 && n <= 15))
{
// "dig.its" and "digits[000].0": the digits after the point move by
// one byte ('0's follow the digits)
#if JSON_DTOA_SSE2
// NOLINTBEGIN(portability-simd-intrinsics)
// (in the register: reading the digits back from memory right after
// storing them waits until the stores are done)
const __m128i at = _mm_set1_epi8(static_cast<char>(n));
const __m128i index = _mm_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
const __m128i before = _mm_cmpgt_epi8(at, index);
const __m128i after = _mm_cmpgt_epi8(index, at);
const __m128i text = _mm_or_si128(_mm_or_si128(_mm_and_si128(s, before), _mm_and_si128(_mm_slli_si128(s, 1), after)),
_mm_andnot_si128(_mm_or_si128(before, after), _mm_set1_epi8('.')));
std::memcpy(first, &text, 16);
first[16] = static_cast<char>(_mm_extract_epi16(s, 7) >> 8);
first[17] = s16;
// NOLINTEND(portability-simd-intrinsics)
#elif JSON_DTOA_NEON
const uint8x16_t index = vcombine_u8(vcreate_u8(0x0706050403020100u), vcreate_u8(0x0F0E0D0C0B0A0908u));
const uint8x16_t at = vdupq_n_u8(static_cast<std::uint8_t>(n));
const uint8x16_t after_point = vbslq_u8(vcgtq_u8(index, at), vextq_u8(vdupq_n_u8(0), s, 15), vdupq_n_u8('.'));
vst1q_u8(reinterpret_cast<std::uint8_t*>(first), vbslq_u8(vcltq_u8(index, at), s, after_point)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
first[16] = static_cast<char>(vgetq_lane_u8(s, 15));
first[17] = s16;
#else
store_16(first);
first[16] = s16;
std::uint64_t after_point[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(after_point, first + n, 16);
std::memcpy(first + n + 1, after_point, 16);
first[n] = '.';
#endif
return first + (n >= len ? n + 2 : len + 1);
}
if (n <= 0 && n > -4)
{
// "0.[000]digits"
std::memset(first, '0', 8);
first[1] = '.';
store_16(first + 2 - n);
first[18 - n] = s16;
return first + 2 - n + len;
}
// d.igitse+XX, with at least two exponent digits (as append_exponent())
store_16(first + 1);
first[17] = s16;
first[0] = first_digit;
first[1] = '.';
char* const end = first + (len == 1 ? 1 : len + 1);
const int e = n - 1;
const auto ea = static_cast<unsigned>(e < 0 ? -e : e);
const bool three = ea >= 100;
end[0] = 'e';
end[1] = e < 0 ? '-' : '+';
end[2] = static_cast<char>('0' + (three ? ea / 100 : (ea / 10) % 10));
end[3] = static_cast<char>('0' + (three ? (ea / 10) % 10 : ea % 10));
end[4] = static_cast<char>('0' + (ea % 10));
return end + (three ? 5 : 4);
}
/// the powers of ten up to 10^16
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept
{
static const std::array<std::uint64_t, 17> powers =
{
{
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u,
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u
}
};
return powers;
}
/*!
@brief digits * 10^exp, as write_decimal() writes it, for the digits of a
double that need no conversion (count digits, at most 15, the first not 0;
trailing zeros allowed): extended to 16 digits and written by write_shortest()
@return a pointer past the text; up to 41 bytes at @a first are written
(some beyond the returned end)
*/
JSON_HEDLEY_NON_NULL(1)
JSON_HEDLEY_RETURNS_NON_NULL
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);
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});
}
/// as write_short_decimal(), counting the digits (not 0, less than 10^15)
JSON_HEDLEY_NON_NULL(1)
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noexcept
{
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
// floor(log10(2^bits)) + 1 digits, or one less
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);
return write_short_decimal(first, digits, count, exp);
}
/// a positive finite float (other than double): Grisu2 and format_buffer()
template<typename FloatType>
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* write_positive(char* first, const char* last, FloatType value)
{
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
// Compute v = buffer * 10^decimal_exponent.
// The decimal digits are stored in the buffer, which needs to be interpreted
// as an unsigned decimal integer.
// len is the length of the buffer, i.e., the number of decimal digits.
int len = 0;
int decimal_exponent = 0;
shortest_digits(first, len, decimal_exponent, value);
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
// Format the buffer like printf("%.*g", prec, value)
constexpr int kMinExp = -4;
// Use digits10 here to increase compatibility with version 2.
constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
JSON_ASSERT(last - first >= kMaxExp + 2);
JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
}
/// a positive finite double: the shortest digits (Zmij), laid out by
/// write_shortest() (through a local buffer if [first, last) is shorter than
/// the 41 bytes it may write)
JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
inline char* write_positive(char* first, const char* last, double value)
{
static_assert(std::numeric_limits<double>::is_iec559 && std::numeric_limits<double>::digits == 53,
"internal error: the conversion of Zmij needs IEEE 754 binary64 doubles");
std::uint64_t bits = 0;
std::memcpy(&bits, &value, sizeof(bits));
const zmij::shortest_decimal d = zmij::to_shortest(bits);
if (JSON_HEDLEY_LIKELY(last - first >= 41))
{
return write_shortest(first, d);
}
std::array<char, 64> buf; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
const auto len = static_cast<std::size_t>(write_shortest(buf.data(), d) - buf.data());
JSON_ASSERT(static_cast<std::size_t>(last - first) >= len);
std::memcpy(first, buf.data(), len);
return first + len;
}
} // namespace dtoa_impl
/*!
@@ -1535,6 +1064,7 @@ JSON_HEDLEY_NON_NULL(1, 2)
JSON_HEDLEY_RETURNS_NON_NULL
char* to_chars(char* first, const char* last, FloatType value)
{
static_cast<void>(last); // maybe unused - fix warning
JSON_ASSERT(std::isfinite(value));
// Use signbit(value) instead of (value < 0) since signbit works for -0.
@@ -1560,7 +1090,28 @@ char* to_chars(char* first, const char* last, FloatType value)
JSON_HEDLEY_DIAGNOSTIC_POP
#endif
return dtoa_impl::write_positive(first, last, value);
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
// Compute v = buffer * 10^decimal_exponent.
// The decimal digits are stored in the buffer, which needs to be interpreted
// as an unsigned decimal integer.
// len is the length of the buffer, i.e., the number of decimal digits.
int len = 0;
int decimal_exponent = 0;
dtoa_impl::grisu2(first, len, decimal_exponent, value);
JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);
// Format the buffer like printf("%.*g", prec, value)
constexpr int kMinExp = -4;
// Use digits10 here to increase compatibility with version 2.
constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;
JSON_ASSERT(last - first >= kMaxExp + 2);
JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);
return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
}
} // namespace detail
@@ -23,7 +23,6 @@
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/iteration_proxy.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/std_fs.hpp>
@@ -287,20 +286,9 @@ struct external_constructor<value_t::object>
/////////////
#ifdef JSON_HAS_CPP_17
// whether storing the value of a std::optional<T> cannot throw; MSVC 2017
// evaluates std::is_nothrow_assignable as true even if T's to_json throws, so
// the exception would call std::terminate (#5642)
#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER < 1920
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::false_type;
#else
template<typename BasicJsonType, typename T>
using is_nothrow_optional_to_json = std::is_nothrow_assignable<BasicJsonType&, const T&>;
#endif
template<typename BasicJsonType, typename T,
enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(is_nothrow_optional_to_json<BasicJsonType, T>::value)
void to_json(BasicJsonType& j, const std::optional<T>& opt) noexcept(std::is_nothrow_assignable<BasicJsonType&, const T&>::value)
{
if (opt.has_value())
{
@@ -376,7 +364,7 @@ inline void to_json(BasicJsonType& j, EnumType e) noexcept
{
using underlying_type = typename std::underlying_type<EnumType>::type;
static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;
external_constructor<integral_value_t>::construct(j, bool_aware_static_cast<underlying_type>(e));
external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));
}
#endif // JSON_DISABLE_ENUM_SERIALIZATION
@@ -396,9 +384,6 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGE_VIEW_CONVERSION
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
&& !is_enum_keyed_map<CompatibleArrayType>::value
#endif
,
int > = 0 >
@@ -453,33 +438,6 @@ inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)
external_constructor<value_t::object>::construct(j, obj);
}
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
// store a map with enum keys as an object, using the enum's own to_json for the
// keys (e.g., from NLOHMANN_JSON_SERIALIZE_ENUM); without the macro, such maps
// are stored as arrays of [key, value] pairs
template < typename BasicJsonType, typename EnumKeyedMap,
enable_if_t < is_enum_keyed_map<EnumKeyedMap>::value&& !is_basic_json<EnumKeyedMap>::value, int > = 0 >
inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
{
typename BasicJsonType::object_t obj;
for (const auto& p : map)
{
BasicJsonType key = p.first;
if (JSON_HEDLEY_UNLIKELY(!key.is_string()))
{
JSON_THROW(type_error::create(302, concat("type must be string, but is ", key.type_name()), &key));
}
auto& key_string = *key.template get_ptr<typename BasicJsonType::string_t*>();
if (JSON_HEDLEY_UNLIKELY(!obj.emplace(key_string, BasicJsonType(p.second)).second))
{
JSON_THROW(type_error::create(318, concat("duplicate object key '", key_string, "'"), &key));
}
}
external_constructor<value_t::object>::construct(j, std::move(obj));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
{
@@ -1,238 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2025 Victor Zverovich <https://github.com/vitaut/zmij>
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // uint32_t, uint64_t
#include <nlohmann/detail/abi_macros.hpp>
#include <nlohmann/detail/bit_ops.hpp>
#include <nlohmann/detail/input/pow5_table.hpp>
#include <nlohmann/detail/macro_scope.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
/*!
@brief the shortest decimal representation of a double
A C++11 port of the conversion of Zmij by Victor Zverovich
(https://github.com/vitaut/zmij, MIT license): the shortest decimal in the
rounding interval of a double, the closest one if there are several. Zmij
credits Xiang JunBo (producing the shorter candidate without a division) and
Dougall Johnson (the compressed powers of ten). The powers of ten are taken
from the table for number parsing (pow5_table.hpp) where it holds them, and
computed from the compressed tables of Zmij beyond it.
*/
namespace zmij
{
/// significand * 10^exponent
struct decimal
{
std::uint64_t significand;
int exponent;
};
/// the compressed powers of ten of Zmij
inline const std::array<std::uint64_t, 28>& pow10_minor() noexcept
{
static const std::array<std::uint64_t, 28> table =
{
{
0x8000000000000000u, 0xa000000000000000u, 0xc800000000000000u, 0xfa00000000000000u, 0x9c40000000000000u,
0xc350000000000000u, 0xf424000000000000u, 0x9896800000000000u, 0xbebc200000000000u, 0xee6b280000000000u,
0x9502f90000000000u, 0xba43b74000000000u, 0xe8d4a51000000000u, 0x9184e72a00000000u, 0xb5e620f480000000u,
0xe35fa931a0000000u, 0x8e1bc9bf04000000u, 0xb1a2bc2ec5000000u, 0xde0b6b3a76400000u, 0x8ac7230489e80000u,
0xad78ebc5ac620000u, 0xd8d726b7177a8000u, 0x878678326eac9000u, 0xa968163f0a57b400u, 0xd3c21bcecceda100u,
0x84595161401484a0u, 0xa56fa5b99019a5c8u, 0xcecb8f27f4200f3au
}
};
return table;
}
/// (high, low) pairs
inline const std::array<std::uint64_t, 50>& pow10_major() noexcept
{
static const std::array<std::uint64_t, 50> table =
{
{
0xaddcb9e83c6b1793u, 0xdf4abe242a1bbf3eu, 0xaf8e5410288e1b6fu, 0x07ecf0ae5ee44ddau, 0xb1442798f49ffb4au, 0x99cd11cfdf41779du,
0xb2fe3f0b8599ef07u, 0x861fa7e6dcb4aa15u, 0xb4bca50b065abe63u, 0x0fed077a756b53aau, 0xb67f6455292cbf08u, 0x1a3bc84c17b1d543u,
0xb84687c269ef3bfbu, 0x3d5d514f40eea742u, 0xba121a4650e4ddebu, 0x92f34d62616ce413u, 0xbbe226efb628afeau, 0x890489f70a55368cu,
0xbdb6b8e905cb600fu, 0x5400e987bbc1c921u, 0xbf8fdb78849a5f96u, 0xde98520472bdd034u, 0xc16d9a0095928a27u, 0x75b7053c0f178294u,
0xc350000000000000u, 0x0000000000000000u, 0xc5371912364ce305u, 0x6c28000000000000u, 0xc722f0ef9d80aad6u, 0x424d3ad2b7b97ef6u,
0xc913936dd571c84cu, 0x03bc3a19cd1e38eau, 0xcb090c8001ab551cu, 0x5cadf5bfd3072cc6u, 0xcd036837130890a1u, 0x36dba887c37a8c10u,
0xcf02b2c21207ef2eu, 0x94f967e45e03f4bcu, 0xd106f86e69d785c7u, 0xe13336d701beba52u, 0xd31045a8341ca07cu, 0x1ede48111209a051u,
0xd51ea6fa85785631u, 0x552a74227f3ea566u, 0xd732290fbacaf133u, 0xa97c177947ad4096u, 0xd94ad8b1c7380874u, 0x18375281ae7822bdu,
0xdb68c2ca82ed2a05u, 0xa67398db9f6820e1u
}
};
return table;
}
/// one bit per power: whether the computed value is one unit too large
inline const std::array<std::uint32_t, 21>& pow10_fixups() noexcept
{
static const std::array<std::uint32_t, 21> table =
{
{
0x8d8fc810u, 0x06100293u, 0x19000000u, 0x00100000u, 0x00000908u, 0x00000000u, 0x04e00300u, 0x3807e0b2u, 0x3d83d793u, 0x0006f5ccu,
0x00000000u, 0xffff0000u, 0x8076337du, 0x4ff45ba0u, 0x09405033u, 0x034376d9u, 0x09000000u, 0x4e100501u, 0x076d14dcu, 0xf964f45eu,
0x0000003du
}
};
return table;
}
/// the 128-bit significand of 10^k, rounded down, for k in [-307, 341]
/// (compute_pow10 of Zmij)
inline uint128_parts compute_pow10(int k) noexcept
{
const auto i = static_cast<unsigned>(k + 307);
const std::uint64_t m = pow10_minor()[(i + 24) % 28];
const std::size_t j = 2 * static_cast<std::size_t>((i + 24) / 28);
const std::uint64_t h_hi = pow10_major()[j];
const std::uint64_t h_lo = pow10_major()[j + 1];
const std::uint64_t h1 = full_multiplication(h_lo, m).high;
const std::uint64_t c0 = h_lo * m;
const std::uint64_t c1 = h1 + (h_hi * m);
const std::uint64_t c2 = (c1 < h1 ? 1u : 0u) + full_multiplication(h_hi, m).high;
uint128_parts r{};
if ((c2 >> 63u) != 0)
{
r.high = c2;
r.low = c1;
}
else
{
r.high = (c2 << 1u) | (c1 >> 63u);
r.low = (c1 << 1u) | (c0 >> 63u);
}
r.low -= (pow10_fixups()[i >> 5u] >> (i & 31u)) & 1u;
return r;
}
/// The 128-bit significand of 10^k, rounded down, for k in [-342, 341].
/// Up to 10^308, the table for number parsing holds the same significands
/// (those of 5^k), except for k in [-27, -1], where it holds them one unit
/// larger (as the Eisel-Lemire algorithm needs them).
inline uint128_parts pow10(int k) noexcept
{
if (k > pow5_128_largest_power)
{
return compute_pow10(k); // (only for the smallest doubles)
}
const auto i = 2 * static_cast<std::size_t>(k - pow5_128_smallest_power);
uint128_parts r{pow5_128()[i + 1], pow5_128()[i]};
const std::uint64_t adjust = static_cast<unsigned>(k + 27) < 27u ? 1u : 0u;
r.high -= r.low < adjust ? 1u : 0u;
r.low -= adjust;
return r;
}
/// (x_hi * 2^64 + x_lo) * y >> 64, as 128 bits
inline uint128_parts umul192_hi128(std::uint64_t x_hi, std::uint64_t x_lo, std::uint64_t y) noexcept
{
const uint128_parts p = full_multiplication(x_hi, y);
uint128_parts r{};
r.low = p.low + full_multiplication(x_lo, y).high;
r.high = p.high + (r.low < p.low ? 1u : 0u);
return r;
}
/// (x * y + c) >> 64
inline std::uint64_t umul128_add_hi64(std::uint64_t x, std::uint64_t y, std::uint64_t c) noexcept
{
const uint128_parts p = full_multiplication(x, y);
return p.high + (p.low + c < p.low ? 1u : 0u);
}
/// the result of Zmij: the shorter candidate and, if that is outside the
/// rounding interval, the digit after it (16 bytes: returned in registers)
struct shortest_decimal
{
std::uint64_t integral; ///< the shorter candidate (15 or 16 digits for normal doubles)
int exponent; ///< the decimal exponent of the digit after it
unsigned char digit; ///< the digit after it (if has_digit)
bool has_digit; ///< whether the shortest decimal is integral * 10 + digit
};
/// The shortest decimal in the rounding interval of a positive finite double
/// given by its bits, the closest one if there are several (to_decimal of
/// Zmij, which keeps the last digit apart: the 15 or 16 digits before it can be
/// converted without a multiplication by 10 first). Always inlined: GCC
/// otherwise calls it, and its result goes through memory.
JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexcept
{
constexpr int extra_shift = 9;
const auto raw_exp = static_cast<int>((bits >> 52u) & 0x7FFu);
std::uint64_t bin_sig = bits & ((std::uint64_t{1} << 52u) - 1);
// a power of two has a narrower interval below (except the smallest normal)
const bool regular = bin_sig != 0 || raw_exp <= 1;
const int bin_exp = (raw_exp == 0 ? 1 : raw_exp) - 1075;
if (raw_exp != 0)
{
bin_sig |= std::uint64_t{1} << 52u;
}
// floor(log10(2^bin_exp)), or floor(log10(3/4 * 2^bin_exp)) for the irregular case
const int dec_exp = ((bin_exp * 315653) - (regular ? 0 : 131072)) >> 20;
// scaled by 10^(-dec_exp - 1): the integral part is the shorter candidate
const int shift = bin_exp + ((-(dec_exp + 1) * 217707) >> 16) + 1 + extra_shift;
const uint128_parts p10 = pow10(-dec_exp - 1);
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
const std::uint64_t fractional = (p.high << static_cast<unsigned>(64 - extra_shift)) | (p.low >> static_cast<unsigned>(extra_shift));
std::uint64_t digit = 0;
bool round_up = false;
bool round_down = false;
if (JSON_HEDLEY_LIKELY(regular))
{
const std::uint64_t half_ulp = (p10.high >> static_cast<unsigned>(extra_shift + 1 - shift)) + (1 - (bin_sig & 1u));
round_up = fractional + half_ulp < fractional;
round_down = half_ulp > fractional;
// the last digit of the longer candidate, rounded to nearest
digit = umul128_add_hi64(fractional, 10, (std::uint64_t{1} << 63u) + 6);
if (fractional == (std::uint64_t{1} << 62u))
{
digit = 2; // 2.5 rounds to 2
}
}
else
{
const std::uint64_t half_ulp = p10.high >> static_cast<unsigned>(extra_shift + 1 - shift);
round_up = half_ulp > ~std::uint64_t{0} - fractional;
round_down = (half_ulp >> 1u) > fractional;
digit = umul128_add_hi64(fractional, 10, (std::uint64_t{1} << 63u) - 1);
const std::uint64_t lowest = umul128_add_hi64(fractional - (half_ulp >> 1u), 10, ~std::uint64_t{0});
digit = digit < lowest ? lowest : digit;
}
integral += round_up ? 1u : 0u;
// if the shorter candidate is outside the rounding interval: one digit more
return shortest_decimal{integral, dec_exp, static_cast<unsigned char>(digit), !round_up && !round_down};
}
/// The shortest decimal in the rounding interval of a positive finite double
/// given by its bits, as one number. The significand can end in zeros.
inline decimal to_decimal(std::uint64_t bits) noexcept
{
const shortest_decimal d = to_shortest(bits);
if (d.has_digit)
{
return decimal{(d.integral * 10) + d.digit, d.exponent};
}
return decimal{d.integral, d.exponent + 1};
}
} // namespace zmij
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
File diff suppressed because it is too large. Load diff
+2 -34
View File
@@ -221,7 +221,7 @@ class lexer : public lexer_base<BasicJsonType>
// scan functions
/////////////////////
/// 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
/// to get(). On success, advances the adapter and the position counters
/// exactly as those 4 get() calls would (a hex digit is never '\n', so
@@ -2070,39 +2070,6 @@ scan_number_done:
// read the next character and ignore whitespace
skip_whitespace();
return scan_after_whitespace();
}
/*!
@brief scan the next token when the caller expects a separator (':' or
',') most of the time
After an object key the next token is almost always ':', after a value
inside an object or array almost always ','. Testing for that character
first is a compare and a well-predicted branch, where the switch in
scan_after_whitespace() is an indirect jump through a table. Anything else
goes through the switch, so the result is the same as scan()'s.
May only be called after scan() has run once (the BOM check is skipped).
*/
token_type scan_expecting(token_type expected_type)
{
JSON_ASSERT(expected_type == token_type::name_separator || expected_type == token_type::value_separator);
JSON_ASSERT(position.chars_read_total > 0);
const char_int_type expected_char = static_cast<unsigned char>((expected_type == token_type::name_separator) ? ':' : ',');
skip_whitespace();
if (JSON_HEDLEY_LIKELY(current == expected_char))
{
return expected_type;
}
return scan_after_whitespace();
}
private:
/// the part of scan() after the leading whitespace: skip comments and
/// scan the token that starts with current
token_type scan_after_whitespace()
{
// ignore comments
while (ignore_comments && current == '/')
{
@@ -2182,6 +2149,7 @@ scan_number_done:
}
}
private:
/// input adapter
InputAdapterType ia;
@@ -249,9 +249,8 @@ template<typename FloatType>
using native_float_t = typename std::conditional<std::numeric_limits<FloatType>::digits == 24, float, double>::type;
/// the value of the eight ASCII digits in @a v (see read_eight_bytes()), three
/// multiplications instead of eight (after simdjson and fast_float); always
/// inlined, as GCC otherwise calls it in the number loops
JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noexcept
/// multiplications instead of eight (after simdjson and fast_float)
inline std::uint32_t parse_eight_digits(std::uint64_t v) noexcept
{
v = ((v & 0x0F0F0F0F0F0F0F0Fu) * 2561u) >> 8u;
v = ((v & 0x00FF00FF00FF00FFu) * 6553601u) >> 16u;
+4 -11
View File
@@ -260,7 +260,7 @@ class parser
}
// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -423,7 +423,7 @@ class parser
{
// comma -> next value
// or end of array (ignore_trailing_commas = true)
if (get_token_expecting(token_type::value_separator))
if (get_token() == token_type::value_separator)
{
// parse a new value
get_token();
@@ -463,7 +463,7 @@ class parser
// comma -> next value
// or end of object (ignore_trailing_commas = true)
if (get_token_expecting(token_type::value_separator))
if (get_token() == token_type::value_separator)
{
get_token();
@@ -484,7 +484,7 @@ class parser
}
// parse separator (:)
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -528,13 +528,6 @@ class parser
return last_token = m_lexer.scan();
}
/// get next token from lexer; true if it is the separator @a expected_type
/// (name_separator or value_separator), which it usually is
bool get_token_expecting(token_type expected_type)
{
return (last_token = m_lexer.scan_expecting(expected_type)) == expected_type;
}
std::string exception_message(const token_type expected, const std::string& context)
{
std::string error_msg = "syntax error ";
-24
View File
@@ -88,13 +88,9 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_string_t/
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())
operator string_t() const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return to_string();
}
#endif
#ifndef JSON_NO_IO
/// @brief write string representation of the JSON pointer to stream
@@ -1033,13 +1029,9 @@ class json_pointer
/// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))
bool operator==(const string_t& rhs) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return *this == json_pointer(rhs);
}
#endif
/// @brief 3-way compares two JSON pointers
template<typename RefStringTypeRhs>
@@ -1117,26 +1109,18 @@ template<typename RefStringTypeLhs,
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
const StringType& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return lhs == json_pointer<RefStringTypeLhs>(rhs);
}
#endif
template<typename RefStringTypeRhs,
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))
inline bool operator==(const StringType& lhs,
const json_pointer<RefStringTypeRhs>& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
}
#endif
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
@@ -1150,26 +1134,18 @@ template<typename RefStringTypeLhs,
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,
const StringType& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return !(lhs == rhs);
}
#endif
template<typename RefStringTypeRhs,
typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>
JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))
inline bool operator!=(const StringType& lhs,
const json_pointer<RefStringTypeRhs>& rhs)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return !(lhs == rhs);
}
#endif
template<typename RefStringTypeLhs, typename RefStringTypeRhs>
inline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,
-4
View File
@@ -888,10 +888,6 @@
#define JSON_DISABLE_ENUM_SERIALIZATION 0
#endif
#ifndef JSON_DELETE_DEPRECATED_FUNCTIONS
#define JSON_DELETE_DEPRECATED_FUNCTIONS 0
#endif
#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0
#endif
@@ -21,8 +21,6 @@
#undef JSON_NO_UNIQUE_ADDRESS
#undef JSON_DISABLE_ENUM_SERIALIZATION
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
#undef JSON_DTOA_SSE2
#undef JSON_DTOA_NEON
#ifndef JSON_TEST_KEEP_MACROS
#undef JSON_CATCH
@@ -45,8 +43,6 @@
#undef JSON_PRECISE_STREAM_POSITION
#undef JSON_STRICT_NUL_HANDLING
#undef JSON_STRICT_BINARY_UTF8
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
@@ -438,30 +438,6 @@ template<typename BasicJsonType, typename CompatibleObjectType>
struct is_compatible_object_type
: is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};
template<typename T>
using insert_result_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()));
template<typename T>
using insert_result_second_t = decltype(std::declval<T&>().insert(std::declval<const value_type_t<T>&>()).second);
// a map-like type (std::map, std::unordered_map, ...) whose keys are enums; see
// JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
template<typename T, typename = void>
struct is_enum_keyed_map : std::false_type {};
template<typename T>
struct is_enum_keyed_map <
T, enable_if_t < is_detected<mapped_type_t, T>::value&&
is_detected<key_type_t, T>::value >>
{
// maps with non-unique keys (std::multimap, std::unordered_multimap, ...)
// are excluded, because an object cannot hold duplicate keys; they are
// detected by insert() returning an iterator instead of a pair<iterator, bool>
// NOLINTNEXTLINE(modernize-type-traits) we use C++11
static constexpr bool value = std::is_enum<typename T::key_type>::value &&
!(is_detected<insert_result_t, T>::value && !is_detected<insert_result_second_t, T>::value);
};
template<typename BasicJsonType, typename ConstructibleObjectType,
typename = void>
struct is_constructible_object_type_impl : std::false_type {};
@@ -906,21 +882,6 @@ T conditional_static_cast(U value)
return value;
}
// like conditional_static_cast, but converts to bool by comparing with zero,
// because MSVC 2015 warns about any conversion to bool (C4800), even with an
// explicit cast; used for enums whose underlying type is bool
template < typename T, typename U, enable_if_t < !std::is_same<T, bool>::value, int > = 0 >
T bool_aware_static_cast(U value)
{
return conditional_static_cast<T>(value);
}
template<typename T, typename U, enable_if_t<std::is_same<T, bool>::value, int> = 0>
bool bool_aware_static_cast(U value)
{
return value != U();
}
template<typename... Types>
using all_integral = conjunction<std::is_integral<Types>...>;
@@ -127,7 +127,6 @@ class binary_writer
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.317 if @a j is not an object
@throw type_error.321 if a value nested in @a j is discarded
*/
void write_bson(const BasicJsonType& j)
{
@@ -159,7 +158,6 @@ class binary_writer
@param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_cbor(const BasicJsonType& j)
{
@@ -324,7 +322,7 @@ class binary_writer
case value_t::discarded:
default:
throw_on_discarded(j, "CBOR");
break;
}
}
@@ -384,7 +382,6 @@ class binary_writer
/*!
@param[in] j JSON value to serialize
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_msgpack(const BasicJsonType& j)
{
@@ -658,7 +655,7 @@ class binary_writer
case value_t::discarded:
default:
throw_on_discarded(j, "MessagePack");
break;
}
}
@@ -671,7 +668,6 @@ class binary_writer
@param[in] bjdata_version which BJData version to use, default is draft2
@throw type_error.316 if a string value or an object key is not valid
UTF-8
@throw type_error.321 if @a j or a value nested in it is discarded
*/
void write_ubjson(const BasicJsonType& j, const bool use_count,
const bool use_type, const bool add_prefix = true,
@@ -905,7 +901,7 @@ class binary_writer
case value_t::discarded:
default:
throw_on_discarded(j, use_bjdata ? "BJData" : "UBJSON");
break;
}
}
@@ -925,15 +921,6 @@ class binary_writer
}
private:
/*!
@brief throws because @a j is discarded and cannot be serialized
@throw type_error.321 always
*/
JSON_HEDLEY_NO_RETURN static void throw_on_discarded(const BasicJsonType& j, const char* format_name)
{
JSON_THROW(type_error::create(321, concat("cannot serialize discarded value to ", format_name), &j));
}
//////////
// BSON //
//////////
@@ -1185,7 +1172,6 @@ class binary_writer
into a byte, before anything is written
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
anything is written
@throw type_error.321 if @a j is discarded
*/
std::size_t calc_bson_value_size(const BasicJsonType& j)
{
@@ -1212,12 +1198,10 @@ class binary_writer
case value_t::null:
return 0ul;
case value_t::discarded:
throw_on_discarded(j, "BSON");
// LCOV_EXCL_START
case value_t::object:
case value_t::array:
case value_t::discarded:
default:
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
return 0ul;
@@ -1254,12 +1238,10 @@ class binary_writer
case value_t::null:
return write_bson_null(name);
case value_t::discarded:
throw_on_discarded(j, "BSON");
// LCOV_EXCL_START
case value_t::object:
case value_t::array:
case value_t::discarded:
default:
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
return;
@@ -1326,8 +1308,6 @@ class binary_writer
byte, before anything is written
@throw type_error.316 if a string value or a key is not valid UTF-8,
before anything is written
@throw type_error.321 if a value nested in @a document is discarded,
before anything is written
*/
std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
{
+16 -45
View File
@@ -1366,9 +1366,8 @@ class serializer
/*!
@brief dump an integer
Dump a given integer, appending it to @ref write_buffer (directly: copying
the digits from another buffer right after writing them waits until the
stores are done).
Dump a given integer, appending it to @ref write_buffer. Works internally with
@a number_buffer.
@param[in] x integer number (signed or unsigned) to dump
@tparam NumberType either @a number_integer_t or @a number_unsigned_t
@@ -1403,57 +1402,33 @@ class serializer
return;
}
// use a pointer to fill the buffer (room for as much as number_buffer holds)
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
{
flush();
}
auto* buffer_ptr = write_buffer.data() + write_buffer_pos;
// use a pointer to fill the buffer
auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto)
number_unsigned_t abs_value;
// one byte for the minus sign
unsigned int n_chars = 0;
unsigned int n_chars{};
if (is_negative_number(x))
{
*buffer_ptr = '-';
abs_value = remove_sign(static_cast<number_integer_t>(x));
n_chars = 1;
// account one more byte for the minus sign
n_chars = 1 + count_digits(abs_value);
}
else
{
abs_value = static_cast<number_unsigned_t>(x);
n_chars = count_digits(abs_value);
}
// up to 16 digits: eight at a time (as the digits of floats), written
// without leading zeros
if (abs_value < 10000000000000000u)
{
const std::uint64_t value = abs_value;
const std::uint64_t upper = value / 100000000u;
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)
char* const p = buffer_ptr + n_chars;
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
n_chars += 8 - leading;
if (upper != 0)
{
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(value - (upper * 100000000u)) + 0x3030303030303030u);
n_chars += 8;
}
write_buffer_pos += n_chars;
return;
}
n_chars += count_digits(abs_value);
// spare 1 byte for '\0'
JSON_ASSERT(n_chars < number_buffer.size() - 1);
// jump to the end to generate the string from backward,
// so we later avoid reversing the result
buffer_ptr += n_chars;
buffer_ptr += static_cast<typename decltype(number_buffer)::difference_type>(n_chars);
// Fast int2ascii implementation inspired by "Fastware" talk by Andrei Alexandrescu
// See: https://www.youtube.com/watch?v=o4-CwDo2zpg
@@ -1476,13 +1451,14 @@ class serializer
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
}
write_buffer_pos += n_chars;
put_buffer(number_buffer, n_chars);
}
/*!
@brief dump a floating-point number
Dump a given floating-point number, appending it to @ref write_buffer.
Dump a given floating-point number, appending it to @ref write_buffer. Works internally
with @a number_buffer.
@param[in] x floating-point number to dump
*/
@@ -1509,15 +1485,10 @@ class serializer
void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{
// directly into the write buffer: copying the text from number_buffer
// right after to_chars() wrote it waits until its stores are done
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + number_buffer.size() > write_buffer.size()))
{
flush();
}
auto* begin = write_buffer.data() + write_buffer_pos;
auto* begin = number_buffer.data();
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
write_buffer_pos += static_cast<std::size_t>(end - begin);
put_buffer(number_buffer, static_cast<std::size_t>(end - begin));
}
JSON_HEDLEY_NON_NULL(1)
+13 -18
View File
@@ -164,12 +164,14 @@ class builder
if (p[1] == '/')
{
p += 2;
// (as in parse(), a null byte is the end of the input, so it is
// left for the caller to see)
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
{
++p;
}
if (NulIsEnd && p != e && *p == 0)
{
++p; // as in parse(), a null byte ends the comment like a line break
}
return p;
}
if (p[1] == '*')
@@ -826,19 +828,14 @@ indent_done:
const auto idx = static_cast<std::uint32_t>(emit(k, 0, 0, static_cast<std::size_t>(p - b), 0) - base);
if (depth != 0)
{
const frame f = {cur_idx, cur_count, cur_is_object};
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
{
// field by field: a frame put together on the stack and
// copied would be read back wider than it was written,
// and that load waits until the stores are done
frame& f = cold.shallow[depth - 1];
f.idx = cur_idx;
f.count = cur_count;
f.is_object = cur_is_object;
cold.shallow[depth - 1] = f;
}
else
{
cold.deep.push_back(frame{cur_idx, cur_count, cur_is_object});
cold.deep.push_back(f);
}
}
++depth;
@@ -854,21 +851,19 @@ indent_done:
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
if (--depth != 0)
{
frame f{};
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
{
const frame& f = cold.shallow[depth - 1];
cur_idx = f.idx;
cur_count = f.count;
cur_is_object = f.is_object;
f = cold.shallow[depth - 1];
}
else
{
const frame f = cold.deep.back();
f = cold.deep.back();
cold.deep.pop_back();
cur_idx = f.idx;
cur_count = f.count;
cur_is_object = f.is_object;
}
cur_idx = f.idx;
cur_count = f.count;
cur_is_object = f.is_object;
}
}
@@ -10,7 +10,7 @@
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t
#include <cstdint> // uint32_t
#include <cstring> // memcpy
#include <functional> // less
#include <map> // map
@@ -41,9 +41,7 @@ struct document_data
node* inline_tape = nullptr; ///< node array allocated together with this header
std::size_t inline_cap = 0;
std::string arena{}; ///< decoded strings that contained escapes // NOLINT(readability-redundant-member-init)
std::size_t arena_size = 0; ///< bytes of decoded strings at base[1] (the arena, or those of a loaded image)
std::string owned{}; ///< owned copy of the input, if any // NOLINT(readability-redundant-member-init)
std::vector<std::uint8_t> owned_image{}; ///< a loaded image the document owns (the text and the decoded strings point into it) // NOLINT(readability-redundant-member-init)
// hash indexes of large objects (see object_index.hpp)
static constexpr std::uint32_t index_min_members = 128;
-602
View File
@@ -1,602 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
#include <cstring> // memcmp, memcpy
#include <limits> // numeric_limits
#include <string> // string
#include <vector> // vector
#include <nlohmann/json.hpp>
#include <nlohmann/detail/view/document_data.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/number.hpp>
#include <nlohmann/detail/view/object_index.hpp>
#include <nlohmann/detail/view/scan.hpp>
// Images: a document stored so that loading it needs no parsing.
//
// Layout (little-endian): a 64-byte header, the nodes, the text (the source,
// followed by the number tokens written by edits), a NUL, the decoded strings
// (followed by the strings written by edits), a NUL. The idea is that of
// zero-copy formats such as FlatBuffers (https://github.com/google/flatbuffers)
// and YaFF (https://github.com/yandex/yaff); no code is taken from them.
// check_image follows the idea of FlatBuffers' Verifier (bounds and
// structure) and also checks what the parser guarantees about strings and
// numbers, so that reading and serializing a checked image is safe and yields
// valid JSON.
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
namespace view
{
/// how load() checks an image
enum class image_check
{
/// everything the parser guarantees: structure and bounds, strings (valid
/// UTF-8; source strings without quotes, backslashes, and control
/// characters), and numbers (well-formed, matching the stored values)
full,
/// structure and bounds only: reading and serializing are safe, but a
/// crafted image can yield invalid UTF-8, strings that serialize to
/// invalid JSON, or numbers that differ from their text
bounds,
/// none: for images from a trusted source only (a damaged image is
/// undefined behavior)
none,
};
struct image_header
{
std::array<char, 4> magic; ///< "NJVI"
std::uint32_t version; ///< 1
std::uint64_t node_count;
std::uint64_t text_size;
std::uint64_t arena_size;
std::array<std::uint64_t, 4> reserved; ///< zero (for later versions)
};
static_assert(sizeof(image_header) == 64, "the image header must be 64 bytes");
constexpr std::uint32_t image_version = 1;
/// the largest node count and text or string size of an image (as for parsed
/// documents, offsets and counts must fit 32 bits)
constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
/// Copy the current structure of an edited document into nodes in document
/// order, as the parser would have written them. Text written by edits is
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
/// are not finite become null, as dump() writes them.
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
{
struct frame
{
const node* cur;
const node* end;
std::size_t index; ///< the container's node in out
std::uint32_t count;
bool object;
};
std::vector<frame> stack;
const auto string_node = [&](const node & s)
{
node r = s;
r.extra = 0;
r.flags = static_cast<std::uint8_t>(s.flags & node_flags::storage);
if (r.flags == node_flags::edited)
{
r.off = static_cast<std::uint32_t>(arena_size + arena_tail.size());
arena_tail.append(d.str(s), s.len);
r.flags = node_flags::escaped;
}
return r;
};
const auto emit = [&](const node * v)
{
node r = *v;
switch (static_cast<value_t>(v->kind))
{
case value_t::object:
case value_t::array:
r.flags = 0;
r.extra = 0;
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
r.len = 0; // counted below
r.next = 0; // set when the container is complete
stack.push_back(frame{d.first_child_edited(v), d.child_end_edited(v), out.size(), 0, v->kind == static_cast<std::uint8_t>(value_t::object)});
break;
case value_t::string:
r = string_node(*v);
break;
case value_t::number_integer:
case value_t::number_unsigned:
if ((v->flags & node_flags::storage) == node_flags::edited)
{
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
text_tail.append(d.str(*v), number_length(*v));
}
r.flags = 0;
break;
case value_t::number_float:
if ((v->flags & node_flags::storage) == node_flags::edited)
{
const char* const t = d.str(*v);
if (t[0] == 'n' || t[0] == 'i' || (v->len > 1 && t[1] == 'i'))
{
r = node{}; // nan and infinity: null, as dump() writes them
r.kind = static_cast<std::uint8_t>(value_t::null);
break;
}
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
text_tail.append(t, v->len);
r.extra = 0xFFFFu; // the digit layout is not recorded
}
r.flags = 0;
break;
case value_t::boolean:
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
break;
case value_t::null:
case value_t::binary:
case value_t::discarded:
default:
r.flags = 0;
break;
}
out.push_back(r);
};
emit(d.tape);
while (!stack.empty())
{
frame& top = stack.back();
if (top.cur == top.end)
{
node& c = out[top.index];
c.len = top.count;
c.next = static_cast<std::uint32_t>(out.size() - top.index);
stack.pop_back();
continue;
}
++top.count;
const node* v = nullptr;
if (top.object)
{
out.push_back(string_node(*top.cur));
v = document_data::deref(top.cur + 1);
top.cur = document_data::after(top.cur + 1);
}
else
{
v = document_data::deref(top.cur);
top.cur = document_data::after(top.cur);
}
emit(v); // may grow the stack (top is not used afterwards)
}
}
/// the document as an image
inline std::vector<std::uint8_t> save_image(const document_data& d)
{
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size;
const node* nodes = d.tape;
std::size_t count = d.tape_size;
std::vector<node> compacted;
std::string text_tail;
std::string arena_tail;
if (d.edits)
{
compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
nodes = compacted.data();
count = compacted.size();
}
const std::size_t text_size = d.size + text_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))
{
// LCOV_EXCL_START (4 GiB)
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP
}
image_header h{};
h.magic = {{'N', 'J', 'V', 'I'}};
h.version = image_version;
h.node_count = count;
h.text_size = text_size;
h.arena_size = total_arena;
std::vector<std::uint8_t> image(sizeof(h) + (count * sizeof(node)) + text_size + 1 + total_arena + 1);
std::uint8_t* o = image.data();
std::memcpy(o, &h, sizeof(h));
o += sizeof(h);
std::memcpy(o, nodes, count * sizeof(node));
// the hash indexes are rebuilt by load()
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)
{
node n = nodes[i];
n.extra = 0;
std::memcpy(o + (i * sizeof(node)), &n, sizeof(node));
}
}
o += count * sizeof(node);
const auto append = [&o](const char* s, std::size_t n)
{
if (n != 0)
{
std::memcpy(o, s, n);
o += n;
}
};
append(d.src, d.size);
append(text_tail.data(), text_tail.size());
*o++ = 0;
append(d.base[1], arena_size);
append(arena_tail.data(), arena_tail.size());
*o = 0;
return image;
}
/// whether a number node matches its token the way the parser records it
/// (after the bounds check)
inline bool check_number(const node& n, const unsigned char* text)
{
const std::size_t len = number_length(n);
const unsigned char* const s = text + n.off;
const unsigned char* const e = s + len;
const unsigned char* p = s;
const bool negative = *p == '-';
p += negative ? 1 : 0;
const unsigned char* const int_start = p;
if (p == e)
{
return false;
}
if (*p == '0')
{
++p;
}
else if (*p >= '1' && *p <= '9')
{
while (p != e && is_digit(*p))
{
++p;
}
}
else
{
return false;
}
const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0;
bool is_float = false;
if (p != e && *p == '.')
{
const unsigned char* const f0 = ++p;
while (p != e && is_digit(*p))
{
++p;
}
if (p == f0)
{
return false;
}
frac_digits = static_cast<std::size_t>(p - f0);
is_float = true;
}
std::int64_t exponent = 0;
if (p != e && (*p | 0x20u) == 'e')
{
++p;
const bool exp_negative = p != e && *p == '-';
p += (p != e && (*p == '+' || *p == '-')) ? 1 : 0;
if (p == e || !is_digit(*p))
{
return false;
}
while (p != e && is_digit(*p))
{
exponent = exponent < 100000 ? (exponent * 10) + (*p - '0') : exponent;
++p;
}
exponent = exp_negative ? -exponent : exponent;
is_float = true;
}
if (p != e)
{
return false;
}
if (n.kind == static_cast<std::uint8_t>(value_t::number_float))
{
// the digit layout the parser records (or "many", as compaction
// writes it), and a finite value
const auto layout = static_cast<std::uint16_t>((int_digits < 255 ? int_digits : 255) | ((frac_digits < 255 ? frac_digits : 255) << 8u));
if (n.extra != layout && n.extra != 0xFFFFu)
{
return false;
}
// parse() rejects floats that overflow; as there, only a number whose
// magnitude could reach 1e308 needs the conversion
if (static_cast<std::int64_t>(int_digits) + exponent > 300)
{
const auto v = float_value<double>(reinterpret_cast<const char*>(s), n); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
return v <= (std::numeric_limits<double>::max)() && v >= -(std::numeric_limits<double>::max)();
}
return true;
}
// integers: the token's value is the stored one; number_integer nodes of
// edits can be non-negative (as basic_json keeps the type of a value)
const bool integer = n.kind == static_cast<std::uint8_t>(value_t::number_integer);
if (is_float || int_digits > 20 || (negative && !integer))
{
return false;
}
// (at most 19 digits cannot overflow; 20 digits are compared with 2^64 - 1)
if (int_digits == 20 && std::memcmp(int_start, "18446744073709551615", 20) > 0)
{
return false;
}
std::uint64_t m = 0;
for (const unsigned char* d = int_start; d != int_start + int_digits; ++d)
{
m = (m * 10) + static_cast<std::uint64_t>(*d - '0');
}
if (integer && m > (negative ? std::uint64_t{1} << 63u : (std::uint64_t{1} << 63u) - 1))
{
return false;
}
return integer_bits(n) == (negative ? 0 - m : m);
}
/// Check the nodes of a loaded image against its text and decoded strings:
/// kinds, flags, and `extra`; extents and element counts of arrays and
/// objects; keys; bounds; string contents (source strings as the parser
/// leaves them: no quotes, backslashes, or control characters; all strings
/// valid UTF-8); and number tokens.
inline bool check_image(const node* nodes, std::size_t count, const unsigned char* text, std::size_t text_size,
const unsigned char* arena, std::size_t arena_size, bool full)
{
struct frame
{
std::size_t end;
std::uint32_t len;
std::uint32_t seen;
bool object;
bool expect_key;
};
std::vector<frame> stack;
const auto check_string = [&](const node & n) -> bool
{
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
{
return false;
}
const bool decoded = (n.flags & node_flags::escaped) != 0;
const unsigned char* const base = decoded ? arena : text;
const std::size_t limit = decoded ? arena_size : text_size;
if (n.off > limit || n.len > limit - n.off)
{
return false;
}
if (!full)
{
return true;
}
const unsigned char* const b = base + n.off;
return decoded ? valid_utf8_prefix(b, n.len) == n.len : scan_string_run(b, b + n.len) == b + n.len;
};
// bounds of a number token; the recorded digit layout must lie within it
const auto number_in_bounds = [&](const node & n) -> bool
{
const std::size_t len = number_length(n);
if (len == 0 || n.off > text_size || len > text_size - n.off)
{
return false;
}
if (n.kind != static_cast<std::uint8_t>(value_t::number_float))
{
return (n.extra >> 8u) == 0;
}
// float_value() reads the sign, the integer digits, and the point and
// fraction digits the layout records (a layout of more than 19 digits
// means the general conversion, which stays within the token)
const std::size_t int_digits = n.extra & 0xFFu;
const std::size_t frac_digits = n.extra >> 8u;
const std::size_t need = (text[n.off] == '-' ? 1u : 0u) + int_digits + (frac_digits != 0 ? frac_digits + 1 : 0);
return int_digits + frac_digits > 19 || need <= len;
};
std::size_t i = 0;
for (;;)
{
// close finished arrays and objects
while (!stack.empty() && i == stack.back().end)
{
const frame f = stack.back();
if (f.seen != f.len || (f.object && !f.expect_key))
{
return false;
}
stack.pop_back();
if (!stack.empty())
{
++stack.back().seen;
stack.back().expect_key = true;
}
}
if (i == count)
{
return stack.empty();
}
if (i != 0 && stack.empty())
{
return false; // nodes after the root
}
const node& n = nodes[i];
if (!stack.empty() && stack.back().object && stack.back().expect_key)
{
if (n.kind != static_cast<std::uint8_t>(value_t::string) || !check_string(n))
{
return false;
}
stack.back().expect_key = false;
++i;
continue;
}
bool complete = true;
switch (static_cast<value_t>(n.kind))
{
case value_t::null:
// (the offset of a literal is read to size the output of dump())
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
{
return false;
}
break;
case value_t::boolean:
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
{
return false;
}
break;
case value_t::string:
if (!check_string(n))
{
return false;
}
break;
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
if (n.flags != 0 || !number_in_bounds(n) || (full && !check_number(n, text)))
{
return false;
}
break;
case value_t::array:
case value_t::object:
{
const std::size_t limit = stack.empty() ? count : stack.back().end;
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
{
return false;
}
stack.push_back(frame{i + n.next, n.len, 0, n.kind == static_cast<std::uint8_t>(value_t::object), true});
complete = false;
break;
}
case value_t::binary:
case value_t::discarded:
default:
return false;
}
++i;
if (complete && !stack.empty())
{
++stack.back().seen;
stack.back().expect_key = true;
}
}
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{
throw_parse_error(116, concat("invalid json_document image: ", what));
}
/// Read an image into d. The text and the decoded strings stay in the image;
/// the nodes are copied (so that they are aligned, and edits can change them).
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
{
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header))
{
throw_invalid_image("too short");
}
image_header h{};
std::memcpy(&h, image, sizeof(h));
// (the reserved fields are for later versions)
if (std::memcmp(h.magic.data(), "NJVI", 4) != 0 || h.version != image_version
|| (h.reserved[0] | h.reserved[1] | h.reserved[2] | h.reserved[3]) != 0)
{
throw_invalid_image("unknown format");
}
const std::size_t room = size - sizeof(h);
if (h.node_count == 0 || h.node_count > room / sizeof(node) || h.node_count >= image_limit || h.text_size >= image_limit || h.arena_size >= image_limit)
{
throw_invalid_image("sizes out of range");
}
const auto count = static_cast<std::size_t>(h.node_count);
const auto text_size = static_cast<std::size_t>(h.text_size);
const auto arena_size = static_cast<std::size_t>(h.arena_size);
const std::size_t text_at = sizeof(h) + (count * sizeof(node));
// the text, a NUL, the decoded strings, a NUL, and nothing after them
if (size - text_at < 2 || text_size > size - text_at - 2 || arena_size != size - text_at - text_size - 2
|| image[text_at + text_size] != 0 || image[size - 1] != 0)
{
throw_invalid_image("sizes out of range");
}
d.discarded = true;
d.edits.reset();
d.base[2] = nullptr;
d.owned.clear();
if (d.owned_image.empty() || image != d.owned_image.data())
{
d.owned_image.clear();
}
d.arena.clear();
d.indexes.clear();
d.index_slots.clear();
d.large_objects.clear();
d.tape_size = 0;
d.reserve(count);
std::memcpy(d.tape, image + sizeof(h), count * sizeof(node));
d.tape_size = count;
const std::uint8_t* const text = image + text_at;
const std::uint8_t* const arena = text + text_size + 1;
d.src = reinterpret_cast<const char*>(text); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
d.size = text_size;
d.base[0] = d.src;
d.base[1] = reinterpret_cast<const char*>(arena); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
d.arena_size = arena_size;
if (check != image_check::none && !check_image(d.tape, count, text, text_size, arena, arena_size, check == image_check::full))
{
throw_invalid_image("the check failed");
}
// the hash indexes of large objects, as after parsing
for (std::size_t i = 0; i < count; ++i)
{
node& n = d.tape[i];
if (n.kind == static_cast<std::uint8_t>(value_t::object))
{
n.extra = 0;
if (n.len >= document_data::index_min_members)
{
d.large_objects.push_back(static_cast<std::uint32_t>(i));
}
}
}
build_object_indexes(d);
d.discarded = false;
}
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
@@ -22,7 +22,5 @@
#undef NLOHMANN_VIEW_NEON
#undef NLOHMANN_VIEW_SSE2
#undef NLOHMANN_VIEW_SSSE3
#undef NLOHMANN_VIEW_SSSE3_DISPATCH
#undef NLOHMANN_VIEW_SSSE3_TARGET
#undef NLOHMANN_VIEW_VECTOR
#undef NLOHMANN_VIEW_VECTOR_UTF8
+30 -67
View File
@@ -26,76 +26,43 @@ namespace detail
namespace view
{
/*!
@brief locate the decimal point and the end of the mantissa of a float token
Also checks that the token is a JSON number. Tokens of the parser and of edits
always are; an image loaded with image_check::bounds can hold any bytes, which
must not reach the conversion (it expects a well-formed token).
*/
inline bool float_token_layout(const char* first, const char* last, std::size_t& dot, std::size_t& mantissa_end) noexcept
{
const auto digit = [last](const char* q)
{
return q != last && is_digit(static_cast<unsigned char>(*q));
};
const char* p = first;
p += (p != last && *p == '-') ? 1 : 0;
if (!digit(p) || (*p == '0' && digit(p + 1)))
{
return false;
}
while (digit(p))
{
++p;
}
dot = std::string::npos;
if (p != last && *p == '.')
{
dot = static_cast<std::size_t>(p - first);
if (!digit(++p))
{
return false;
}
while (digit(p))
{
++p;
}
}
mantissa_end = static_cast<std::size_t>(p - first);
if (p != last && (*p == 'e' || *p == 'E'))
{
++p;
p += (p != last && (*p == '+' || *p == '-')) ? 1 : 0;
if (!digit(p))
{
return false;
}
while (digit(p))
{
++p;
}
}
return p == last;
}
/*!
@brief the value of the float token of a node, as parse() converts it
Uses the lexer's conversion (detail::convert_float), so that the values are
bit-identical to parse(): float and double are converted without allocation
and independent of the locale. A token that is not a JSON number (only in a
damaged image loaded with image_check::bounds) yields 0.
and independent of the locale. The digit layout recorded while parsing locates
the decimal point and the exponent without scanning the token.
*/
template<typename FloatType>
NLOHMANN_VIEW_NOINLINE FloatType float_value(const char* first, const node& n)
{
const char* const last = first + n.len;
std::size_t dot = 0;
std::size_t mantissa_end = 0;
if (NLOHMANN_VIEW_UNLIKELY(!float_token_layout(first, last, dot, mantissa_end)))
const std::size_t neg = first[0] == '-' ? 1 : 0;
const std::size_t int_digits = n.extra & 0xFFu;
const std::size_t frac_digits = n.extra >> 8u;
std::size_t dot = std::string::npos;
std::size_t mantissa_end = n.len;
if (int_digits != 255 && frac_digits != 255)
{
return FloatType{};
dot = frac_digits != 0 ? neg + int_digits : std::string::npos;
mantissa_end = neg + int_digits + (frac_digits != 0 ? 1 + frac_digits : 0);
}
else
{
// more digits than the layout records: locate them
for (std::size_t i = 0; i < n.len; ++i)
{
if (first[i] == '.')
{
dot = i;
}
else if (first[i] == 'e' || first[i] == 'E')
{
mantissa_end = i;
break;
}
}
}
return convert_float<FloatType>(first, last, dot, mantissa_end);
}
@@ -126,20 +93,16 @@ NLOHMANN_VIEW_ALWAYS_INLINE float_significand layout_decimal(const unsigned char
}
if (p != e)
{
// [eE][+-]digits; huge exponents saturate (the parser rejected
// overflow). The token is not read beyond e, and the digits are taken
// as unsigned, so that a token that is not well-formed (a damaged
// image loaded with image_check::bounds) yields a wrong value, but no
// overflow.
// [eE][+-]digits; huge exponents saturate (the parser rejected overflow)
++p;
const bool exp_negative = p != e && *p == '-';
p += (p != e && (*p == '-' || *p == '+')) ? 1 : 0;
const bool exp_negative = *p == '-';
p += (*p == '-' || *p == '+') ? 1 : 0;
std::int64_t exp_value = 0;
for (; p != e; ++p)
{
if (exp_value < 0x10000000)
{
exp_value = (exp_value * 10) + static_cast<unsigned char>(*p - '0');
exp_value = (exp_value * 10) + (*p - '0');
}
}
q += exp_negative ? -exp_value : exp_value;
+4 -19
View File
@@ -67,19 +67,13 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint16_t load16(const unsigned char* p) noexcep
/// in predicted branches: 16 for keys, whose lengths repeat from record to
/// record, and 8 for string values (Value) where a vector loop follows, as
/// their lengths vary more. Longer runs continue 16 bytes at a time with NEON
/// or SSE2, else eight bytes at a time. With SSE2, the run is checked 16 bytes
/// at a time from its first byte instead: on x86-64, one compare that finds
/// the end of most keys and short values is faster than a branch per byte (on
/// AArch64, where a NEON mask costs more and branches predict well, slower).
/// or SSE2, else eight bytes at a time.
template<bool Value = false>
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned char* p, const unsigned char* e) noexcept
{
const std::uint8_t* plain = string_plain();
for (;;)
{
#if NLOHMANN_VIEW_SSE2
p = vector_plain_run(p, e);
#else
if (e - p >= 16)
{
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
@@ -113,7 +107,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
#endif
continue;
}
#endif
while (p != e && plain[*p] != 0)
{
++p;
@@ -122,23 +115,15 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
{
return p;
}
#if !NLOHMANN_VIEW_SSE2
stop:
#endif
if (*p < 0x80)
{
return p; // quote, backslash, or control character
}
#if NLOHMANN_VIEW_VECTOR_UTF8
#if NLOHMANN_VIEW_SSSE3_DISPATCH
if (NLOHMANN_VIEW_LIKELY(cpu_has_ssse3()))
#endif
{
// non-ASCII: the vector check, out of line
return scan_string_vector(p, e, plain);
}
#endif
#if !NLOHMANN_VIEW_VECTOR_UTF8 || NLOHMANN_VIEW_SSSE3_DISPATCH
// non-ASCII: the vector check, out of line
return scan_string_vector(p, e, plain);
#else
// non-ASCII: a run of well-formed sequences (the library's check, so
// that exactly what json::parse accepts is accepted)
do
+10 -529
View File
@@ -31,11 +31,7 @@ namespace view
{
/// append-only output buffer: writes through a raw pointer into a string that
/// is resized ahead, and trimmed by finish(). The estimate is reserved, and the
/// string grows in steps of 64 KiB within it: resize() fills the new bytes
/// with zeros (before C++23, a string cannot grow without), and a small step
/// is filled while the writer is about to use it, in the cache, instead of
/// filling the whole estimate in memory first.
/// is resized ahead, and trimmed by finish()
template<typename StringType>
class output_buffer
{
@@ -79,49 +75,17 @@ class output_buffer
m_pos += n;
}
/// the write position and the end of the writable space, for a writer
/// that keeps the position in a local variable (set_cursor() hands it back)
char* cursor() const noexcept
{
return m_pos;
}
char* limit() const noexcept
{
return m_end;
}
void set_cursor(char* p) noexcept
{
m_pos = p;
}
private:
/// the size of a growth step (a function: std::min() takes a reference,
/// which a static constexpr member does not have before C++17)
static constexpr std::size_t step() noexcept
{
return 65536;
}
static StringType& sized(StringType& out, std::size_t estimate)
{
out.reserve(estimate);
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
out.resize((std::max)(estimate, static_cast<std::size_t>(64)));
return out;
}
NLOHMANN_VIEW_NOINLINE void grow(std::size_t n)
{
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
// (a step does not go beyond the reserved estimate, so that a good
// estimate is never copied to a larger allocation)
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
if (size > m_out.capacity())
{
m_out.reserve((std::max)(m_out.capacity() * 2, size));
}
m_out.resize(size);
m_out.resize((std::max)(m_out.size() * 2, used + n + 256));
m_pos = &m_out[0] + used;
m_end = &m_out[0] + m_out.size();
}
@@ -131,105 +95,6 @@ class output_buffer
char* m_end;
};
/// The length of the run at s that dump() writes unchanged without
/// ensure_ascii: all bytes but quotes, backslashes, and control characters.
/// Unlike detail::string_bulk_run(), non-ASCII bytes are not validated: the
/// strings of a document are valid UTF-8 (a damaged image loaded with
/// image_check::bounds can have others, which are then written unchanged).
inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexcept
{
constexpr std::uint64_t ones = 0x0101010101010101ull;
constexpr std::uint64_t high = 0x8080808080808080ull;
std::size_t i = 0;
for (; i + 8 <= n; i += 8)
{
const std::uint64_t v = read_eight_bytes(s + i);
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
const std::uint64_t stop = (((q - ones) & ~q) | ((b - ones) & ~b) | ((v - 0x2020202020202020ull) & ~v)) & high;
if (stop != 0)
{
// the lowest flagged byte is the first stop: borrows only flag bytes above a true one
return i + (static_cast<std::size_t>(count_trailing_zeros(stop)) / 8);
}
}
for (; i < n; ++i)
{
if (s[i] == '"' || s[i] == '\\' || s[i] < 0x20)
{
return i;
}
}
return n;
}
/// 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
/// 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
/// vector and returns the new storage), so its pointers stay in registers.
template<typename T>
class small_stack
{
public:
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)
{}
small_stack(const small_stack&) = delete;
small_stack(small_stack&&) = delete;
small_stack& operator=(const small_stack&) = delete;
small_stack& operator=(small_stack&&) = delete;
~small_stack() = default;
NLOHMANN_VIEW_ALWAYS_INLINE void push_back(const T& x)
{
if (NLOHMANN_VIEW_UNLIKELY(m_top == m_end))
{
const std::size_t used = size();
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_top = m_begin + used;
m_end = m_begin + capacity;
}
*m_top++ = x;
}
NLOHMANN_VIEW_ALWAYS_INLINE T& back() noexcept
{
return m_top[-1];
}
NLOHMANN_VIEW_ALWAYS_INLINE void pop_back() noexcept
{
--m_top;
}
NLOHMANN_VIEW_ALWAYS_INLINE bool empty() const noexcept
{
return m_top == m_begin;
}
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t size() const noexcept
{
return static_cast<std::size_t>(m_top - m_begin);
}
private:
/// the used entries moved to heap storage of the given capacity
NLOHMANN_VIEW_NOINLINE static T* grow(std::vector<T>& heap, const T* begin, std::size_t used, std::size_t capacity)
{
std::vector<T> bigger(capacity);
std::copy(begin, begin + used, bigger.begin());
heap.swap(bigger);
return heap.data();
}
T* m_begin;
T* m_top;
T* m_end;
std::vector<T>* m_heap;
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -264,18 +129,6 @@ class view_serializer
void dump(const node* root)
{
if (!m_style.pretty && !m_style.ensure_ascii)
{
if (m_style.source_numbers)
{
dump_compact<true>(root);
}
else
{
dump_compact<false>(root);
}
return;
}
struct frame
{
const node* pos; ///< next element, or key of the next member
@@ -283,9 +136,7 @@ class view_serializer
bool object;
bool first; ///< nothing written yet
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
std::vector<frame> stack;
const node* n = root;
for (;;)
{
@@ -356,289 +207,6 @@ class view_serializer
}
private:
/*!
@brief the compact output without ensure_ascii (the default dump())
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
buffer's members after each one), and with strings and number tokens of
the source copied by fixed-size moves of 32 bytes where the source has
that many bytes left, instead of a library call per token. The buffer
keeps 64 bytes of slack for the overshoot.
*/
/// a string that is not a plain string of the source (decoded, or written
/// by an edit), without ensure_ascii: runs without characters to escape
/// are copied
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
m_out.put('"');
for (std::size_t i = 0; i < n.len;)
{
const std::size_t run = plain_output_run(s + i, n.len - i);
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// the copies of dump_compact() that are not fixed-size moves (long
/// strings, or near the end of the source); out of line, so that the
/// compiler does not merge the fixed-size moves into this call
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
{
std::memcpy(to, from, n);
}
template<bool SourceNumbers>
void dump_compact(const node* root)
{
struct frame
{
const node* pos; ///< (editable documents) next element, or key of the next member
const node* end;
bool object;
};
std::array<frame, 32> buffer; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::vector<frame> heap;
small_stack<frame> stack(buffer.data(), buffer.size(), heap);
const char* const src = m_doc.src;
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(lim - w) < n + 64))
{
m_out.set_cursor(w);
m_out.reserve(n + 64);
w = m_out.cursor();
lim = m_out.limit();
}
};
// copy n bytes of the source (after room(n))
const auto copy = [&](const char* from, std::size_t n)
{
if (n <= 32 && static_cast<std::size_t>(src_end - from) >= 32)
{
std::memcpy(w, from, 32);
}
else if (n <= 256 && static_cast<std::size_t>(src_end - from) >= n + 32)
{
for (std::size_t i = 0; i < n; i += 32)
{
std::memcpy(w + i, from + i, 32);
}
}
else
{
copy_long(w, from, 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
// that the cursor stays in a register here)
const auto escaped = [&](const node & n)
{
m_out.set_cursor(w);
write_decoded(n);
w = m_out.cursor();
lim = m_out.limit();
};
// Read-only documents: the elements of a container follow it in the
// node array, so the walk goes through the array in order, and a
// frame only needs the end of its container. Editable documents: the
// elements of a moved container live elsewhere, so a frame keeps the
// position of the next element (see navigation).
// The innermost open container is kept in registers (cur; end ==
// nullptr: none), the stack holds the ones around it.
frame cur{nullptr, nullptr, false};
const node* n = root;
for (;;)
{
// write the value at n (read-only documents: and advance n)
bool opened = false;
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
if ((n->flags & node_flags::storage) == 0)
{
room(n->len + 2);
*w++ = '"';
copy(src + n->off, n->len);
*w++ = '"';
}
else
{
escaped(*n);
}
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const std::uint32_t len = number_length(*n);
room(len);
if (Editable && (n->flags & node_flags::storage) != 0)
{
copy_long(w, m_doc.str(*n), len); // a canonical token written by an edit
w += len;
break;
}
const char* const token = src + n->off;
if (!SourceNumbers && NLOHMANN_VIEW_UNLIKELY(len == 2 && token[0] == '-' && token[1] == '0'))
{
*w++ = '0'; // parse() reads -0 as the integer 0
}
else
{
copy(token, len);
}
break;
}
case value_t::number_float:
if (SourceNumbers && (n->flags & node_flags::storage) != node_flags::edited)
{
room(n->len);
copy(src + n->off, n->len);
}
else if (std::is_same<number_float_t, double>::value)
{
room(64);
w = write_double_at(w, *n);
}
else
{
m_out.set_cursor(w);
write_float_node(*n);
w = m_out.cursor();
lim = m_out.limit();
}
break;
case value_t::boolean:
room(8);
if ((n->flags & node_flags::is_true) != 0)
{
literal("true", 4);
}
else
{
literal("false", 5);
}
break;
case value_t::object:
case value_t::array:
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
room(8);
if (n->len == 0)
{
literal(object ? "{}" : "[]", 2);
}
else
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
}
else
{
cur = frame{nullptr, n + n->next, object};
}
opened = true;
}
break;
}
case value_t::null:
room(8);
literal("null", 4);
break;
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
if (!Editable)
{
++n; // the next node: the first element of an opened container, or the node after a scalar
}
// go to the next value: close finished containers, then separate
// (a container just opened has an element)
if (!opened)
{
for (;;)
{
if (cur.end == nullptr)
{
m_out.set_cursor(w);
m_out.finish();
return;
}
if ((Editable ? cur.pos : n) != cur.end)
{
break;
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
stack.pop_back();
}
room(1);
*w++ = ',';
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if ((key.flags & node_flags::storage) == 0)
{
room(key.len + 3);
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
w[1] = ':';
w += 2;
}
else
{
escaped(key);
room(1);
*w++ = ':';
}
if (Editable)
{
n = nav::value(at + 1);
cur.pos = document_data::after(at + 1);
}
else
{
++n;
}
}
else if (Editable)
{
n = nav::value(at);
cur.pos = document_data::after(at);
}
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
@@ -690,7 +258,7 @@ class view_serializer
}
else
{
write_float_node(n);
write_float(float_value<number_float_t>(m_doc, n));
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
@@ -702,83 +270,6 @@ class view_serializer
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
write_float_node(n, std::is_same<number_float_t, double> {});
}
void write_float_node(const node& n, std::false_type /*other*/)
{
write_float(float_value<number_float_t>(m_doc, n));
}
void write_float_node(const node& n, std::true_type /*double*/)
{
m_out.reserve(64);
m_out.set_cursor(write_double_at(m_out.cursor(), n));
}
/*!
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
A token of at most 15 significant digits is written from its digits,
without a conversion: two decimals of at most 15 digits are farther
apart than the rounding interval of a (normal) double (the argument
behind DBL_DIG), so the token's digits are the shortest ones of its
double, which the library's conversion writes (Zmij). Other tokens are
converted from the digits already read.
*/
char* write_double_at(char* w, const node& n)
{
const unsigned int_digits = n.extra & 0xFFu;
const unsigned frac_digits = n.extra >> 8u;
if ((n.flags & node_flags::storage) != node_flags::edited && int_digits + frac_digits <= 19)
{
const auto* const first = reinterpret_cast<const unsigned char*>(m_doc.src + n.off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const float_significand d = layout_decimal(first, first + n.len, int_digits, frac_digits, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the exponent keeps the value far from subnormals and overflow)
if (d.w != 0 && d.w < 1000000000000000u && d.exponent >= -290 && d.exponent <= 290)
{
*w = '-';
w += d.negative ? 1 : 0;
// (without leading zeros, all digits of the token count)
const unsigned char lead = first[d.negative ? 1 : 0];
return lead != '0' ? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(int_digits + frac_digits), static_cast<int>(d.exponent))
: ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(d.exponent));
}
return write_double_value_at(w, decimal_to_float<double>(d)); // (without reading the token again)
}
return write_double_value_at(w, static_cast<double>(float_value<number_float_t>(m_doc, n)));
}
/// n bytes of text at w
static char* write_text_at(char* w, const char* text, std::size_t n) noexcept
{
std::memcpy(w, text, n);
return w + n;
}
/// a double as dump() writes it, at w (64 bytes of room)
static char* write_double_value_at(char* w, double x)
{
// (from the bits: without the checks of to_chars())
std::uint64_t bits = 0;
std::memcpy(&bits, &x, sizeof(bits));
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
{
return write_text_at(w, "null", 4);
}
*w = '-';
w += bits >> 63u;
bits &= ~(std::uint64_t{1} << 63u);
if (bits == 0)
{
return write_text_at(w, "0.0", 3);
}
return ::nlohmann::detail::dtoa_impl::write_shortest(w, ::nlohmann::detail::zmij::to_shortest(bits));
}
/// as serializer::dump_float()
void write_float(number_float_t x)
{
@@ -826,9 +317,7 @@ class view_serializer
m_out.put('"');
}
/// as serializer::dump_escaped(); strings of a document are valid UTF-8,
/// except in a damaged image loaded with image_check::bounds, for which
/// this throws what basic_json::dump() throws for the string
/// as serializer::dump_escaped() for valid UTF-8 (the view has no other)
template<bool EnsureAscii>
void write_escaped(const unsigned char* s, std::size_t n)
{
@@ -852,13 +341,12 @@ class view_serializer
}
std::uint32_t codepoint = s[i];
std::size_t len = 1;
if (codepoint >= 0x80)
if (codepoint >= 0xC0)
{
len = validate_one_utf8(s + i, n - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
len = 2;
if (codepoint >= 0xE0)
{
invalid_utf8(s, n);
return;
len = codepoint >= 0xF0 ? 4 : 3;
}
codepoint &= 0xFFu >> (len + 1);
for (std::size_t k = 1; k < len; ++k)
@@ -871,13 +359,6 @@ class view_serializer
}
}
/// throw what basic_json::dump() throws for a string that is not valid UTF-8
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
{
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
static_cast<void>(dumped);
}
template<bool EnsureAscii>
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
{
+7 -61
View File
@@ -10,7 +10,6 @@
#pragma once
#include <array> // array
#include <atomic> // atomic
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint64_t
@@ -19,13 +18,11 @@
// Vector code for long runs of string bytes. NEON (AArch64) and SSE2 (x86-64)
// belong to the baseline instruction sets and are used by default. The vector
// UTF-8 check needs NEON or SSSE3. SSSE3 is not part of x86-64, and the code
// must not depend on the flags of a translation unit (two translation units
// with different flags would have different definitions of the same inline
// functions): the check is compiled for SSSE3 with a function attribute and
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
// portable check. JSON_VIEW_USE_SSSE3 skips the CPU check (for code compiled
// for SSSE3 anyway); JSON_VIEW_NO_SIMD selects the portable code.
// UTF-8 check needs NEON, or SSSE3 if JSON_VIEW_USE_SSSE3 is defined: SSSE3 is
// not part of x86-64, so it must not depend on the flags of a translation unit
// (two translation units with different flags would have different
// definitions of the same inline functions). JSON_VIEW_NO_SIMD selects the
// portable code.
#if !defined(JSON_VIEW_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN
#include <arm_neon.h>
#define NLOHMANN_VIEW_NEON 1
@@ -44,24 +41,8 @@
#else
#define NLOHMANN_VIEW_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#endif
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
#include <cpuid.h>
#include <tmmintrin.h>
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
#elif NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && defined(_MSC_VER)
// (MSVC compiles intrinsics of any instruction set)
#include <intrin.h>
#include <tmmintrin.h>
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define NLOHMANN_VIEW_SSSE3_TARGET
#else
#define NLOHMANN_VIEW_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
#define NLOHMANN_VIEW_SSSE3_TARGET
#endif
#define NLOHMANN_VIEW_VECTOR (NLOHMANN_VIEW_NEON || NLOHMANN_VIEW_SSE2)
#define NLOHMANN_VIEW_VECTOR_UTF8 (NLOHMANN_VIEW_NEON || NLOHMANN_VIEW_SSSE3 || NLOHMANN_VIEW_SSSE3_DISPATCH)
#define NLOHMANN_VIEW_VECTOR_UTF8 (NLOHMANN_VIEW_NEON || NLOHMANN_VIEW_SSSE3)
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -109,40 +90,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
}
#endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
inline bool cpu_ssse3() noexcept
{
#if defined(_MSC_VER) && !defined(__clang__)
std::array<int, 4> regs {{}};
__cpuid(regs.data(), 1);
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
#else
unsigned eax = 0;
unsigned ebx = 0;
unsigned ecx = 0;
unsigned edx = 0;
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
#endif
}
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
/// that is initialized at compile time, so that neither a guard of a local
/// static nor a global constructor is needed (threads that ask at the same
/// time all store the same answer)
NLOHMANN_VIEW_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
{
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
int state = known.load(std::memory_order_relaxed);
if (NLOHMANN_VIEW_UNLIKELY(state == 0))
{
state = cpu_ssse3() ? 2 : 1;
known.store(state, std::memory_order_relaxed);
}
return state == 2;
}
#endif
#if NLOHMANN_VIEW_VECTOR_UTF8
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
@@ -245,9 +192,8 @@ compares, and the UTF-8 check covers the bytes up to it. Returns where the
string scan stops, like scan_string_run: before ill-formed UTF-8 and for the
last bytes of the input, the bytes are checked one sequence at a time. Out of
line, so that no constants of the check occupy registers in the parse loop.
On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
*/
NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept
NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept
{
using lookup = utf8_lookup4<>;
const unsigned char* block = p;
+88 -329
View File
@@ -613,210 +613,100 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
/// constructor for rvalue binary arrays (internal type)
json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}
private:
// raw, allocation-free transfer of m_data from src to dst: no
// set_parents()/assert_invariant() (the former is O(#children) per
// call under JSON_DIAGNOSTICS, which would make the walk below
// quadratic); dst takes ownership, src is left as value_t::null.
static void take(basic_json& dst, basic_json& src) noexcept
{
dst.m_data.m_type = src.m_data.m_type;
dst.m_data.m_value = src.m_data.m_value;
src.m_data.m_type = value_t::null;
}
// true if v is not an array/object, or is an already-empty one
static bool has_no_children(const basic_json& v) noexcept
{
switch (v.m_data.m_type)
{
case value_t::array:
return v.m_data.m_value.array->empty();
case value_t::object:
return v.m_data.m_value.object->empty();
default:
return true;
}
}
static basic_json& last_child(basic_json& v)
{
if (v.m_data.m_type == value_t::array)
{
return v.m_data.m_value.array->back();
}
JSON_ASSERT(v.m_data.m_type == value_t::object);
return v.m_data.m_value.object->rbegin()->second;
}
// removes the last child of a non-empty array/object v; this never
// allocates, and since it is only ever called when that child is a
// scalar or an already-empty array/object, destroying it never
// recurses more than one level deep (see destroy() below)
static void pop_last_child(basic_json& v)
{
if (v.m_data.m_type == value_t::array)
{
v.m_data.m_value.array->pop_back();
}
else
{
JSON_ASSERT(v.m_data.m_type == value_t::object);
// erase() needs a forward iterator, so std::prev(end()) is
// used here rather than rbegin() (see last_child() above)
v.m_data.m_value.object->erase(std::prev(v.m_data.m_value.object->end()));
}
}
// deallocates the (already empty) array/object held by v; this is
// the same allocator-based free the old recursive implementation
// used, just factored out so every level of the walk in destroy()
// can share it
static void free_container(basic_json& v) noexcept
{
if (v.m_data.m_type == value_t::array)
{
JSON_ASSERT(v.m_data.m_value.array->empty());
AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
}
else
{
JSON_ASSERT(v.m_data.m_type == value_t::object);
JSON_ASSERT(v.m_data.m_value.object->empty());
AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
}
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
}
public:
void destroy_string() noexcept
{
if (string == nullptr)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
AllocatorType<string_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
}
void destroy_binary() noexcept
{
if (binary == nullptr)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
AllocatorType<binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
}
// t must be value_t::array or value_t::object
void destroy_container(value_t t) noexcept
void destroy(value_t t)
{
if (
(t == value_t::object && object == nullptr) ||
(t == value_t::array && array == nullptr)
(t == value_t::array && array == nullptr) ||
(t == value_t::string && string == nullptr) ||
(t == value_t::binary && binary == nullptr)
)
{
// not initialized (e.g., due to exception in the ctor)
return;
}
// Destroy the tree without recursing per nesting level and
// without any heap allocation: a heap-allocated flattening
// stack (the previous implementation) can itself throw
// bad_alloc, which would escape this noexcept destructor and
// terminate the program (#5135).
//
// Instead, walk down the "last child" chain, reversing links
// as we go: cur is the container currently being emptied,
// and prev is its parent (value_t::null when there is none).
// Each parent's last child slot doubles as storage for that
// parent's own parent link while we are below it, so no
// extra memory is needed. We only ever remove a child once
// it is a scalar or an empty array/object, which neither
// allocates nor recurses more than one level deep.
//
// This json_value is not itself a basic_json, so the
// top-level container is first moved into a local stand-in
// ("cur"); a default-constructed basic_json has a null
// pointer in its m_value (see data::m_value's initializer),
// so swapping it with *this leaves this union's own pointer
// null, and it is never looked at or freed a second time.
basic_json cur;
cur.m_data.m_type = t;
using std::swap;
swap(cur.m_data.m_value, *this);
basic_json prev; // value_t::null: no parent
while (true)
if (t == value_t::array || t == value_t::object)
{
if (has_no_children(cur))
// flatten the current json_value to a heap-allocated stack
std::vector<basic_json> stack;
// move the top-level items to stack
if (t == value_t::array)
{
if (prev.m_data.m_type == value_t::null)
stack.reserve(array->size());
std::move(array->begin(), array->end(), std::back_inserter(stack));
}
else
{
stack.reserve(object->size());
for (auto&& it : *object)
{
free_container(cur);
return; // back at the top with nothing left to do
stack.push_back(std::move(it.second));
}
}
while (!stack.empty())
{
// move the last item to a local variable to be processed
basic_json current_item(std::move(stack.back()));
stack.pop_back();
// if current_item is array/object, move
// its children to the stack to be processed later
if (current_item.is_array())
{
std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));
current_item.m_data.m_value.array->clear();
}
else if (current_item.is_object())
{
for (auto&& it : *current_item.m_data.m_value.object)
{
stack.push_back(std::move(it.second));
}
current_item.m_data.m_value.object->clear();
}
// ascend: detach the grandparent link from prev's
// last slot, drop that (now null) slot, free cur
// (it is empty), then move up one level
basic_json gp;
take(gp, last_child(prev));
pop_last_child(prev);
free_container(cur);
take(cur, prev);
take(prev, gp);
continue;
// it's now safe that current_item gets destructed
// since it doesn't have any children
}
basic_json& cur_last_ref = last_child(cur);
if (has_no_children(cur_last_ref))
{
// scalar, or already-empty array/object
pop_last_child(cur);
continue;
}
// descend into the non-empty last child, reversing the
// link: its slot takes over prev, and the child becomes
// the new cur
basic_json tmp;
take(tmp, cur_last_ref);
take(cur_last_ref, prev);
take(prev, cur);
take(cur, tmp);
}
}
void destroy(value_t t)
{
switch (t)
{
case value_t::string:
destroy_string();
case value_t::object:
{
AllocatorType<object_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, object);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);
break;
}
case value_t::array:
{
AllocatorType<array_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, array);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);
break;
}
case value_t::string:
{
AllocatorType<string_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, string);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);
break;
}
case value_t::binary:
destroy_binary();
break;
case value_t::object:
case value_t::array:
destroy_container(t);
{
AllocatorType<binary_t> alloc;
std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);
std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);
break;
}
case value_t::null:
case value_t::boolean:
@@ -825,7 +715,9 @@ public:
case value_t::number_float:
case value_t::discarded:
default:
{
break;
}
}
}
};
@@ -3601,13 +3493,9 @@ public:
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return value(ptr.convert(), default_value);
}
#endif
template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,
detail::enable_if_t <
@@ -3616,13 +3504,9 @@ public:
&& !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return value(ptr.convert(), std::forward<ValueType>(default_value));
}
#endif
/// @brief access the first element
/// @sa https://json.nlohmann.me/api/basic_json/front/
@@ -3980,13 +3864,9 @@ public:
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.contains(this);
}
#endif
/// @}
@@ -4097,13 +3977,9 @@ public:
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ref.items();
}
#endif
/// @brief wrapper to access iterator member functions in range-based for
/// @sa https://json.nlohmann.me/api/basic_json/items/
@@ -4112,13 +3988,9 @@ public:
/// that is, replace `json::iterator_wrapper(j)` with `j.items()`.
JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())
static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ref.items();
}
#endif
/// @brief helper to access iterator member functions in range-based for
/// @sa https://json.nlohmann.me/api/basic_json/items/
@@ -5207,7 +5079,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType>
requires std::is_scalar_v<ScalarType>
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) <= rhs;
}
@@ -5216,7 +5088,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType>
requires std::is_scalar_v<ScalarType>
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) >= rhs;
}
@@ -5237,16 +5109,11 @@ public:
#endif
}
// The friend comparisons with a scalar name the JSON type via decltype of
// their parameter in noexcept, because older MSVC versions do not see the
// class scope there: MSVC 2015 and 2017 take basic_json as the template,
// and MSVC 2019 16.0 rejects member types and template parameters.
/// @brief comparison: equal
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator==(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs == basic_json(rhs);
}
@@ -5255,7 +5122,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_eq/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator==(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) == rhs;
}
@@ -5271,7 +5138,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs != basic_json(rhs);
}
@@ -5280,7 +5147,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_ne/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) != rhs;
}
@@ -5300,7 +5167,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator<(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs < basic_json(rhs);
}
@@ -5309,7 +5176,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_lt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator<(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) < rhs;
}
@@ -5329,7 +5196,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs <= basic_json(rhs);
}
@@ -5338,7 +5205,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_le/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) <= rhs;
}
@@ -5359,7 +5226,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator>(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs > basic_json(rhs);
}
@@ -5368,7 +5235,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_gt/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator>(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) > rhs;
}
@@ -5388,7 +5255,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(lhs)>, ScalarType>::value)
friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return lhs >= basic_json(rhs);
}
@@ -5397,7 +5264,7 @@ public:
/// @sa https://json.nlohmann.me/api/basic_json/operator_ge/
template<typename ScalarType, typename std::enable_if<
std::is_scalar<ScalarType>::value, int>::type = 0>
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<detail::uncvref_t<decltype(rhs)>, ScalarType>::value)
friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept(std::is_nothrow_constructible<basic_json, ScalarType>::value)
{
return basic_json(lhs) >= rhs;
}
@@ -5433,9 +5300,6 @@ public:
return o;
}
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
// the deleted version is a function template after the class, because
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
/// @brief serialize to stream
/// @sa https://json.nlohmann.me/api/operator_ltlt/
/// @deprecated This function is deprecated since 3.0.0 and will be removed in
@@ -5447,7 +5311,6 @@ public:
{
return o << j;
}
#endif
#endif // JSON_NO_IO
/// @}
@@ -5499,16 +5362,12 @@ public:
const bool allow_exceptions = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
basic_json result;
auto p = parser(i.get(), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
p.parse(true, result);
return result;
}
#endif
/// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/
@@ -5538,13 +5397,9 @@ public:
static bool accept(detail::span_input_adapter&& i,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
}
#endif
/// @brief generate SAX events
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
@@ -5605,9 +5460,6 @@ public:
const bool strict = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
auto ia = i.get();
return format == input_format_t::json
@@ -5616,14 +5468,10 @@ public:
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict);
}
#endif
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
#ifndef JSON_NO_IO
#if !JSON_DELETE_DEPRECATED_FUNCTIONS
// the deleted version is a function template after the class, because
// GCC < 5 and Clang < 10 reject deleted friend functions in class templates
/// @brief deserialize from stream
/// @sa https://json.nlohmann.me/api/operator_gtgt/
/// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in
@@ -5635,7 +5483,6 @@ public:
{
return operator>>(i, j);
}
#endif
/// @brief deserialize from stream
/// @sa https://json.nlohmann.me/api/operator_gtgt/
@@ -5961,13 +5808,9 @@ public:
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))
@@ -5975,13 +5818,9 @@ public:
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::cbor, strict, allow_exceptions, error_handler_t::keep, tag_handler);
}
#endif
/// @brief create a JSON value from an input in MessagePack format
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
@@ -6014,26 +5853,18 @@ public:
static basic_json from_msgpack(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_msgpack(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))
static basic_json from_msgpack(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::msgpack, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
@@ -6066,26 +5897,18 @@ public:
static basic_json from_ubjson(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_ubjson(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))
static basic_json from_ubjson(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::ubjson, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BJData format
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
@@ -6112,20 +5935,6 @@ public:
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bjdata, strict, allow_exceptions, error_handler);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bjdata(ptr, ptr + len))
static basic_json from_bjdata(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bjdata(ptr, ptr + len, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BON8 format
/// @sa https://json.nlohmann.me/api/basic_json/from_bon8/
template<typename InputType>
@@ -6149,20 +5958,6 @@ public:
return from_binary_impl(detail::input_adapter(std::move(first), std::move(last)), input_format_t::bon8, strict, allow_exceptions);
}
template<typename T>
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.13.0, from_bon8(ptr, ptr + len))
static basic_json from_bon8(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bon8(ptr, ptr + len, strict, allow_exceptions);
}
#endif
/// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename InputType>
@@ -6194,26 +5989,18 @@ public:
static basic_json from_bson(const T* ptr, std::size_t len,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_bson(ptr, ptr + len, strict, allow_exceptions);
}
#endif
JSON_HEDLEY_WARN_UNUSED_RESULT
JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))
static basic_json from_bson(detail::span_input_adapter&& i,
const bool strict = true,
const bool allow_exceptions = true)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return from_binary_impl(i.get(), input_format_t::bson, strict, allow_exceptions);
}
#endif
/// @}
//////////////////////////
@@ -6233,13 +6020,9 @@ public:
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_unchecked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/
@@ -6251,13 +6034,9 @@ public:
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_unchecked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/at/
@@ -6269,13 +6048,9 @@ public:
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_checked(this);
}
#endif
/// @brief access specified element via JSON Pointer
/// @sa https://json.nlohmann.me/api/basic_json/at/
@@ -6287,13 +6062,9 @@ public:
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const
#if JSON_DELETE_DEPRECATED_FUNCTIONS
= delete;
#else
{
return ptr.get_checked(this);
}
#endif
/// @brief return flattened JSON value
/// @sa https://json.nlohmann.me/api/basic_json/flatten/
@@ -7270,18 +7041,6 @@ std::string format_as(const NLOHMANN_BASIC_JSON_TPL& j)
return j.dump();
}
#if JSON_DELETE_DEPRECATED_FUNCTIONS && !defined(JSON_NO_IO)
/// @brief serialize to stream (deleted; use operator<<(std::ostream&, const basic_json&))
/// @sa https://json.nlohmann.me/api/operator_ltlt/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
std::ostream& operator>>(const NLOHMANN_BASIC_JSON_TPL& j, std::ostream& o) = delete;
/// @brief deserialize from stream (deleted; use operator>>(std::istream&, basic_json&))
/// @sa https://json.nlohmann.me/api/operator_gtgt/
NLOHMANN_BASIC_JSON_TPL_DECLARATION
std::istream& operator<<(NLOHMANN_BASIC_JSON_TPL& j, std::istream& i) = delete;
#endif
NLOHMANN_JSON_NAMESPACE_END
///////////////////////
+8 -67
View File
@@ -25,7 +25,7 @@
#define INCLUDE_NLOHMANN_JSON_VIEW_HPP_
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint32_t
#include <cstdint> // uint32_t
#include <cstring> // memcpy, strlen
#include <iterator> // distance, input_iterator_tag, iterator_traits
#include <map> // map
@@ -53,7 +53,6 @@
#include <nlohmann/detail/view/edit.hpp>
#include <nlohmann/detail/view/edit_storage.hpp>
#include <nlohmann/detail/view/errors.hpp>
#include <nlohmann/detail/view/image.hpp>
#include <nlohmann/detail/view/input.hpp>
#include <nlohmann/detail/view/iterator.hpp>
#include <nlohmann/detail/view/lookup.hpp>
@@ -592,10 +591,8 @@ class basic_json_view
style.indent_char = indent_char;
style.ensure_ascii = ensure_ascii;
style.source_numbers = numbers == number_format::source;
// the compact text is about as long as the source text of the value;
// the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
// the compact text is about as long as the source text of the value
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 64;
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
return out;
}
@@ -937,7 +934,7 @@ class basic_json_document
/// whether the document holds its own copy of the text
bool owns_source() const noexcept
{
return m_data && ((!m_data->owned.empty() && m_data->src == m_data->owned.data()) || !m_data->owned_image.empty());
return m_data && !m_data->owned.empty() && m_data->src == m_data->owned.data();
}
/// number of index nodes (values plus object keys)
@@ -946,7 +943,7 @@ class basic_json_document
return m_data ? m_data->tape_size : 0;
}
/// bytes held by the document (index, decoded strings, owned text or image)
/// bytes held by the document (index, decoded strings, owned text)
std::size_t memory_usage() const noexcept
{
if (!m_data)
@@ -955,7 +952,7 @@ class basic_json_document
}
return sizeof(document_data) + (m_data->inline_cap * sizeof(detail::view::node))
+ (m_data->tape != m_data->inline_tape ? m_data->tape_cap * sizeof(detail::view::node) : 0)
+ m_data->arena.capacity() + m_data->owned.capacity() + m_data->owned_image.capacity()
+ m_data->arena.capacity() + m_data->owned.capacity()
+ (m_data->indexes.capacity() * sizeof(document_data::object_index)) + (m_data->index_slots.capacity() * sizeof(std::uint32_t))
+ (m_data->large_objects.capacity() * sizeof(std::uint32_t))
+ (m_data->edits != nullptr ? m_data->edits->bytes : 0);
@@ -975,10 +972,8 @@ class basic_json_document
// allocate everything first, so that an exception leaves the document
// unchanged
// (the decoded strings of a loaded image stay in the image)
const bool arena_in_use = d.base[1] == d.arena.data();
const bool shrink_arena = d.arena.capacity() > d.arena.size();
std::string arena(shrink_arena && arena_in_use ? d.arena : std::string());
std::string arena(shrink_arena ? d.arena : std::string());
// (edits link to the nodes of the index, which then stays in place)
const bool shrink_tape = d.tape != d.inline_tape && d.tape_size != d.tape_cap && d.edits == nullptr;
const bool into_header = d.tape_size <= d.inline_cap;
@@ -994,62 +989,10 @@ class basic_json_document
if (shrink_arena)
{
d.arena.swap(arena);
if (arena_in_use)
{
d.base[1] = d.arena.data();
}
d.base[1] = d.arena.data();
}
}
////////////
// images //
////////////
/// how load() checks an image (full, bounds, or none)
using image_check = detail::view::image_check;
/// The document as an image that load() reads without parsing: the node
/// index, the text, and the decoded strings. An edited document is
/// written in its current state (floats that are not finite become null,
/// as in dump()).
std::vector<std::uint8_t> save() const
{
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{
detail::view::throw_type_error(320, "cannot save a discarded json_document");
}
return detail::view::save_image(*m_data);
}
/// Read an image written by save(). The image is borrowed: it must stay
/// alive and unchanged while the document is used.
NLOHMANN_VIEW_NODISCARD
static basic_json_document load(const std::uint8_t* image, std::size_t size, const image_check check = image_check::full)
{
basic_json_document d;
d.ensure_data(nullptr, 0);
detail::view::load_image(*d.m_data, image, size, check);
return d;
}
/// read an image (borrowed)
NLOHMANN_VIEW_NODISCARD
static basic_json_document load(const std::vector<std::uint8_t>& image, const image_check check = image_check::full)
{
return load(image.data(), image.size(), check);
}
/// read an image and keep it (no copy)
NLOHMANN_VIEW_NODISCARD
static basic_json_document load(std::vector<std::uint8_t>&& image, const image_check check = image_check::full)
{
basic_json_document d;
d.ensure_data(nullptr, 0);
d.m_data->owned_image = std::move(image);
detail::view::load_image(*d.m_data, d.m_data->owned_image.data(), d.m_data->owned_image.size(), check);
return d;
}
///////////
// edits //
///////////
@@ -1233,7 +1176,6 @@ class basic_json_document
{
d.owned.clear();
}
d.owned_image.clear();
d.src = src;
d.size = size;
d.tape_size = 0;
@@ -1258,7 +1200,6 @@ class basic_json_document
{
d.base[0] = d.src;
d.base[1] = d.arena.data();
d.arena_size = d.arena.size();
detail::view::build_object_indexes(d);
d.discarded = false;
return;
-7680
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -15
View File
@@ -67,10 +67,6 @@
#define JSON_STRICT_BINARY_UTF8 0
#endif
#ifndef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
@@ -113,20 +109,14 @@
#define NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS _ekmo
#else
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// 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(a, b, c, d, e, f, g, h) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g) json_abi ## a ## b ## c ## d ## e ## f ## g
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
@@ -136,8 +126,7 @@
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
File diff suppressed because it is too large. Load diff
-32
View File
@@ -8,7 +8,6 @@ set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_Te
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
if (NOT "" STREQUAL "$ENV{JSON_FORCED_GLOBAL_COMPILE_OPTIONS}")
@@ -289,33 +288,6 @@ elseif(NOT json_32bit_test)
list(FILTER files EXCLUDE REGEX src/unit-32bit.cpp)
endif()
# with JSON_TestShard=<index>/<count>, keep every <count>-th unit test file,
# starting at <index> (the glob is sorted, so the split is stable)
set(test_shard_index 0)
if(NOT "${JSON_TestShard}" STREQUAL "")
if(NOT JSON_TestShard MATCHES "^([0-9]+)/([1-9][0-9]*)$")
message(FATAL_ERROR "JSON_TestShard must be <index>/<count>, e.g. 0/2, not '${JSON_TestShard}'.")
endif()
set(test_shard_index ${CMAKE_MATCH_1})
set(test_shard_count ${CMAKE_MATCH_2})
if(NOT test_shard_index LESS test_shard_count)
message(FATAL_ERROR "JSON_TestShard: the index must be less than the count, not '${JSON_TestShard}'.")
endif()
list(LENGTH files test_file_count)
set(shard_files "")
set(file_position 0)
foreach(file ${files})
math(EXPR file_shard "${file_position} % ${test_shard_count}")
if(file_shard EQUAL test_shard_index)
list(APPEND shard_files ${file})
endif()
math(EXPR file_position "${file_position} + 1")
endforeach()
set(files ${shard_files})
list(LENGTH files shard_file_count)
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
endif()
foreach(file ${files})
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
endforeach()
@@ -333,9 +305,6 @@ if(json_32bit_test_only)
return()
endif()
# the following variants of single test files are only built in the first shard
if(test_shard_index EQUAL 0)
# test legacy comparison of discarded values
json_test_set_test_options(test-comparison_legacy
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
@@ -382,7 +351,6 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
)
endif()
endif()
# *DO NOT* use json_test_set_test_options() below this line
+1 -4
View File
@@ -10,7 +10,7 @@ CXXFLAGS += -std=c++11
CPPFLAGS += -I ../single_include
FUZZER_ENGINE = src/fuzzer-driver_afl.cpp
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer parse_json_view_fuzzer json_view_image_fuzzer
FUZZERS = parse_afl_fuzzer parse_bson_fuzzer parse_cbor_fuzzer parse_msgpack_fuzzer parse_ubjson_fuzzer parse_bjdata_fuzzer parse_bon8_fuzzer parse_json_view_fuzzer
fuzzers: $(FUZZERS)
parse_afl_fuzzer:
@@ -19,9 +19,6 @@ parse_afl_fuzzer:
parse_json_view_fuzzer:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_json_view.cpp -o $@
json_view_image_fuzzer:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-json_view_image.cpp -o $@
parse_bson_fuzzer:
$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(FUZZER_ENGINE) src/fuzzer-parse_bson.cpp -o $@
-4
View File
@@ -48,10 +48,6 @@ TEST_CASE("default namespace")
expected += "_sbu8";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
-4
View File
@@ -49,10 +49,6 @@ TEST_CASE("default namespace without version component")
expected += "_sbu8";
#endif
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
expected += "_ekmo";
#endif
expected += "::basic_json";
// fallback for Clang
+3 -14
View File
@@ -50,8 +50,7 @@ JSON-RPC request (`rpc`):
| dump | serialize a parsed document (compact) |
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
yyjson with numbers read as raw text (`YYJSON_READ_NUMBER_AS_RAW`), without converting them.
documents.
`bench_edit.cpp` measures read-modify-write: parse, apply the same logical edits with each library's own API, and
serialize (compact). Workloads: `patch` (a handful of edits at fixed places) and `update` (edits in every record).
@@ -61,10 +60,7 @@ outputs are checked to describe the same value.
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed
call follows an untimed call of the same engine: otherwise the engine after `json::parse` pays for the allocator
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
slower on citm_catalog traverse).
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`).
The engines do not all offer the same features, which the numbers should be read with:
@@ -72,18 +68,11 @@ The engines do not all offer the same features, which the numbers should be read
|---|---|---|---|---|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
| simdjson DOM | immutable, parser reused | yes | no | |
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
| `json::parse` | owning, mutable DOM | yes | yes | |
Reusing memory matters as much as the parser. simdjson DOM reuses its parser, so it writes into memory it already
touched; a fresh `json_view` document or yyjson document gets new memory for every parse. On Linux, glibc returns large
blocks to the system when they are freed, so every fresh parse of a large document pays a page fault per 4 KiB page:
on x86-64 Linux, a fresh `json_view` parse of jeopardy took about twice as long as a reused one. On macOS on Apple
silicon, with 16 KiB pages, the difference is much smaller. Compare "json_view (reused)" with "simdjson DOM", and the
fresh `json_view` with "simdjson DOM (fresh)" and yyjson.
## Published results
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
+1 -17
View File
@@ -15,8 +15,7 @@
// count, string bytes, sum of numbers) before anything is timed. Workloads:
// parse (build and free a document), traverse (visit every value, convert
// every number), dump (compact), and for json_view also dump with the source
// number text, compared with yyjson writing numbers read as raw text
// (YYJSON_READ_NUMBER_AS_RAW). Results go to bench_corpus.csv.
// number text. Results go to bench_corpus.csv.
#include <nlohmann/json_view.hpp>
#if JSON_VIEW_BENCH_BOOST
@@ -30,7 +29,6 @@
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
@@ -197,11 +195,6 @@ static void walk(const boost::json::value& v, stats& st)
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
@@ -229,7 +222,6 @@ int main(int argc, char** argv)
}
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
json_document reused;
simdjson::dom::parser sj;
for (const auto& path : files)
{
@@ -273,7 +265,6 @@ int main(int argc, char** argv)
};
json_document vd = json_document::parse(s);
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
yyjson_doc* yd_raw = yyjson_read(s.data(), s.size(), YYJSON_READ_NUMBER_AS_RAW);
simdjson::dom::parser sjd;
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
@@ -281,10 +272,8 @@ int main(int argc, char** argv)
{
"parse", {
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
#endif
@@ -306,7 +295,6 @@ int main(int argc, char** argv)
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
{"yyjson (raw numbers)", [&] { std::size_t n = 0; char* o = yyjson_write(yd_raw, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
}
},
};
@@ -318,9 +306,6 @@ int main(int argc, char** argv)
{
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
wl.second[k].fn();
const auto t0 = std::chrono::steady_clock::now();
wl.second[k].fn();
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
@@ -336,7 +321,6 @@ int main(int argc, char** argv)
std::fflush(stdout);
}
yyjson_doc_free(yd);
yyjson_doc_free(yd_raw);
}
std::fclose(csv);
}
@@ -479,11 +479,6 @@ static std::string edit_boost(const std::string& name, const std::string& s, boo
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
@@ -555,9 +550,6 @@ int main(int argc, char** argv)
{
for (std::size_t k = 0; k < engines.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
g_sink = engines[k].second(dc.name, dc.text, update).size();
const auto t0 = std::chrono::steady_clock::now();
for (int b = 0; b < dc.batch; ++b)
{
+2 -16
View File
@@ -10,8 +10,7 @@
//
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
// yyjson yyjson_read(): immutable document, random access
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
// per parse): immutable, random access
// simdjson DOM dom::parser (reused, as recommended): immutable, random access
// references (different feature sets):
// simdjson OD On-Demand: forward-only, lazy
// Boost.JSON owning, mutable DOM (monotonic resource)
@@ -20,8 +19,7 @@
// Workloads: parse (build + free), traverse (visit everything, convert every
// number, touch every string and key), select (a few fields per document),
// dump (compact serialization of the parsed document).
// All engines run interleaved in every round, each timed call after an untimed
// one of the same engine; the best round is reported.
// All engines run interleaved in every round; the best round is reported.
#include <nlohmann/json_view.hpp>
#if JSON_VIEW_BENCH_BOOST
@@ -35,7 +33,6 @@
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <map>
@@ -584,11 +581,6 @@ static double pick_od(const std::string& name, simdjson::ondemand::document& d)
static std::string slurp(const std::string& p)
{
std::ifstream f(p, std::ios::binary);
if (!f)
{
std::fprintf(stderr, "cannot open %s\n", p.c_str());
std::exit(1);
}
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
@@ -665,7 +657,6 @@ int main(int argc, char** argv)
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
#if JSON_VIEW_BENCH_BOOST
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
#endif
@@ -673,7 +664,6 @@ int main(int argc, char** argv)
}});
workloads.push_back({"traverse", {
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
@@ -684,7 +674,6 @@ int main(int argc, char** argv)
}});
workloads.push_back({"select", {
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
@@ -721,9 +710,6 @@ int main(int argc, char** argv)
{
for (std::size_t k = 0; k < wl.second.size(); ++k)
{
// an untimed call first: whatever the previous engine left to the allocator
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
wl.second[k].fn();
const auto t0 = std::chrono::steady_clock::now();
for (int b = 0; b < dc.batch; ++b)
{
+2 -9
View File
@@ -154,14 +154,11 @@ def download_library(name, work):
if not os.path.isfile(archive):
print(f'downloading {pin["url"]}', flush=True)
# the URLs are the https constants in PINNED, and the SHA-256 is checked below
# (into a .part file first, so that an interrupted download is not kept)
urllib.request.urlretrieve(pin['url'], archive + '.part') # nosec B310
os.replace(archive + '.part', archive)
urllib.request.urlretrieve(pin['url'], archive) # nosec B310
with open(archive, 'rb') as f:
digest = hashlib.sha256(f.read()).hexdigest()
if digest != pin['sha256']:
os.remove(archive) # downloaded again by the next run
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]} (removed)')
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]}')
src = os.path.join(work, 'download', pin['dir'])
if not os.path.isdir(src):
with tarfile.open(archive) as t:
@@ -217,10 +214,6 @@ def main():
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
args = ap.parse_args()
# the benchmarks run in the build directory: make the paths absolute
args.data = os.path.abspath(args.data)
args.corpus = [os.path.abspath(f) for f in args.corpus]
args.build_dir = os.path.abspath(args.build_dir)
cxx = os.environ.get('CXX', 'c++')
cc = os.environ.get('CC', 'cc')
-6
View File
@@ -9,12 +9,6 @@ Additionally, `parse_json_view_fuzzer` (`tests/src/fuzzer-parse_json_view.cpp`)
produces, and that a rejected input makes both parsers throw with an identical `what()`. It takes plain JSON text, so it
reuses the `corpus_json` corpus rather than a format of its own.
`json_view_image_fuzzer` (`tests/src/fuzzer-json_view_image.cpp`) tests the images of `json_document` (`save()` and
`load()`). It uses each input twice: as an image, which `load()` must either reject with `parse_error.116` or read
safely (with `image_check::full`, the document must also serialize to the JSON it reads as), and as a JSON text, whose
image must load and serialize to the same text. A corpus of images can be made from JSON files with a small program
that calls `json_document::parse(text).save()`; plain JSON files work as well.
## Corpus creation
For most effective fuzzing, a [corpus](https://llvm.org/docs/LibFuzzer.html#corpus) should be provided. A corpus is a
-95
View File
@@ -1,95 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++ (supporting code)
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
/*
This file implements a test of json_document images suitable for fuzz
testing. The input is used twice:
- as an image: json_document::load() with image_check::full must either throw
a parse_error or yield a document that serializes to the JSON text it reads
as; with image_check::bounds, reading and serializing must be safe (checked
by the sanitizers), and serializing may only throw type_error.316
- as a JSON text: if json_document::parse() accepts it, the image of the
document must load (with every check) and serialize to the same text
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
drivers.
*/
#include <cassert>
#include <cstdint>
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include <nlohmann/json_view.hpp>
// the checks below are assertions; NDEBUG would compile them away
#ifdef NDEBUG
#error "the fuzzer drivers must be built without NDEBUG"
#endif
using json = nlohmann::json;
using json_document = nlohmann::json_document;
using image_check = json_document::image_check;
// see http://llvm.org/docs/LibFuzzer.html
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
// the input as an image
for (const image_check check :
{
image_check::full, image_check::bounds
})
{
json_document d;
try
{
d = json_document::load(data, size, check);
}
catch (const json::parse_error& e)
{
assert(e.id == 116);
continue;
}
std::string dumped;
try
{
dumped = d.root().dump();
}
catch (const json::type_error& e)
{
// invalid UTF-8 can only pass the bounds check
assert(check == image_check::bounds && e.id == 316);
continue;
}
const json j = d.root().materialize();
if (check == image_check::full)
{
assert(json::parse(dumped) == j);
// an image of the loaded document is the input
assert(d.save() == std::vector<std::uint8_t>(data, data + size));
}
}
// the input as a JSON text
const std::string text(reinterpret_cast<const char*>(data), size); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const json_document parsed = json_document::parse(text, false);
if (!parsed.is_discarded())
{
const std::vector<std::uint8_t> image = parsed.save();
for (const image_check check :
{
image_check::full, image_check::bounds, image_check::none
})
{
const json_document loaded = json_document::load(image, check);
assert(loaded.root().dump() == parsed.root().dump());
}
}
return 0;
}
-90
View File
@@ -521,10 +521,6 @@ struct countdown_allocator : std::allocator<T>
TEST_CASE("converting a deeply nested value from another specialization fails cleanly (#5650)")
{
// MSVC 2015's debug STL constructs the containers' debug proxies through
// the allocator in noexcept constructors, so a failing construction crashes
// the program there instead of throwing std::bad_alloc. Nothing to check.
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
using countdown_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
@@ -562,7 +558,6 @@ TEST_CASE("converting a deeply nested value from another specialization fails cl
}
}
CHECK(failures > 0);
#endif
}
namespace
@@ -607,88 +602,3 @@ TEST_CASE("bad my_allocator::construct")
j["test"].push_back("should not leak");
}
}
namespace
{
std::size_t counting_allocator_allocations = 0;
std::size_t counting_allocator_deallocations = 0;
template<class T>
struct counting_allocator : std::allocator<T>
{
using std::allocator<T>::allocator;
T* allocate(std::size_t n)
{
++counting_allocator_allocations;
return std::allocator<T>::allocate(n);
}
void deallocate(T* p, std::size_t n)
{
++counting_allocator_deallocations;
std::allocator<T>::deallocate(p, n);
}
template <class U>
struct rebind
{
using other = counting_allocator<U>;
};
};
} // namespace
TEST_CASE("destructor performs no allocation, only deallocation")
{
// see https://github.com/nlohmann/json/issues/4842 and
// https://github.com/nlohmann/json/issues/5135: destroying nested
// arrays/objects used to allocate a temporary stack (first with
// std::allocator, later - after #4842 - with the provided allocator).
// Since that stack could itself throw bad_alloc from inside the
// noexcept destructor (#5135), destroy() no longer allocates anything:
// it only ever frees what is already there.
using counting_json = nlohmann::basic_json<std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
counting_allocator>;
SECTION("array")
{
auto* j = new counting_json({1, {2, {3, 4}}, 5}); // NOLINT(cppcoreguidelines-owning-memory)
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
SECTION("object")
{
auto* j = new counting_json({{"a", {{"b", {1, 2}}}}, {"c", 3}}); // NOLINT(cppcoreguidelines-owning-memory)
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
SECTION("mixed tree of empty/non-empty arrays and objects")
{
auto* j = new counting_json( // NOLINT(cppcoreguidelines-owning-memory)
{
{"empty_obj", counting_json::object()},
{"empty_arr", counting_json::array()},
{"nested", {{"a", counting_json::array({1, 2, counting_json::object()})}, {"b", 3}}},
{"tail", counting_json::array({counting_json::array({1}), 2, counting_json::array({3})})}
});
const auto allocations_before = counting_allocator_allocations;
const auto deallocations_before = counting_allocator_deallocations;
delete j; // NOLINT(cppcoreguidelines-owning-memory)
CHECK(counting_allocator_allocations == allocations_before);
CHECK(counting_allocator_deallocations > deallocations_before);
}
}
+11 -152
View File
@@ -11,12 +11,7 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cmath>
#include <fstream>
#include <limits>
#include <map>
#include <string>
#include <vector>
#include "make_test_data_available.hpp"
TEST_CASE("Binary Formats" * doctest::skip())
@@ -38,7 +33,7 @@ TEST_CASE("Binary Formats" * doctest::skip())
const auto ubjson_2_size = json::to_ubjson(j, true).size();
const auto ubjson_3_size = json::to_ubjson(j, true, true).size();
CHECK(json_size == 2090234);
CHECK(json_size == 2090303);
CHECK(bjdata_1_size == 1112030);
CHECK(bjdata_2_size == 1224148);
CHECK(bjdata_3_size == 1224148);
@@ -51,16 +46,16 @@ TEST_CASE("Binary Formats" * doctest::skip())
CHECK(ubjson_3_size == 1169069);
CHECK((100.0 * double(json_size) / double(json_size)) == Approx(100.0));
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.201));
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.565));
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.565));
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.511));
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.853));
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.499));
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.528));
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(53.201));
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(58.565));
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(55.930));
CHECK((100.0 * double(bjdata_1_size) / double(json_size)) == Approx(53.199));
CHECK((100.0 * double(bjdata_2_size) / double(json_size)) == Approx(58.563));
CHECK((100.0 * double(bjdata_3_size) / double(json_size)) == Approx(58.563));
CHECK((100.0 * double(bon8_size) / double(json_size)) == Approx(50.509));
CHECK((100.0 * double(bson_size) / double(json_size)) == Approx(85.849));
CHECK((100.0 * double(cbor_size) / double(json_size)) == Approx(50.497));
CHECK((100.0 * double(msgpack_size) / double(json_size)) == Approx(50.526));
CHECK((100.0 * double(ubjson_1_size) / double(json_size)) == Approx(53.199));
CHECK((100.0 * double(ubjson_2_size) / double(json_size)) == Approx(58.563));
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(55.928));
}
SECTION("twitter.json")
@@ -229,139 +224,3 @@ TEST_CASE("Binary Formats" * doctest::skip())
CHECK((100.0 * double(ubjson_3_size) / double(json_size)) == Approx(89.450));
}
}
namespace
{
// the binary formats as function pointers for "Binary formats with narrow number types";
// named functions rather than lambdas, because clang 3.5 cannot convert a lambda
// to a function pointer in the braced initializer of the format table
using narrow_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
using bytes = std::vector<std::uint8_t>;
bytes encode_cbor(const json& j)
{
return json::to_cbor(j);
}
narrow_json decode_cbor(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_cbor(v, true, allow_exceptions);
}
bytes encode_msgpack(const json& j)
{
return json::to_msgpack(j);
}
narrow_json decode_msgpack(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_msgpack(v, true, allow_exceptions);
}
bytes encode_ubjson(const json& j)
{
return json::to_ubjson(j);
}
narrow_json decode_ubjson(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_ubjson(v, true, allow_exceptions);
}
bytes encode_bjdata(const json& j)
{
return json::to_bjdata(j);
}
narrow_json decode_bjdata(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_bjdata(v, true, allow_exceptions);
}
// BSON can only store numbers as object members
bytes encode_bson(const json& j)
{
return json::to_bson(json{{"a", j}});
}
narrow_json decode_bson(const bytes& v, bool allow_exceptions)
{
const auto result = narrow_json::from_bson(v, true, allow_exceptions);
return result.is_discarded() ? result : result.at("a");
}
bytes encode_bon8(const json& j)
{
return json::to_bon8(j);
}
narrow_json decode_bon8(const bytes& v, bool allow_exceptions)
{
return narrow_json::from_bon8(v, true, allow_exceptions);
}
} // namespace
TEST_CASE("Binary formats with narrow number types")
{
// Numbers that do not fit the number types are handled like the lexer
// handles them in JSON text: an integer that fits neither integer type is
// stored as a floating-point number, and a finite floating-point number
// that overflows number_float_t is rejected with out_of_range.406.
struct binary_format
{
const char* name;
bytes (*encode)(const json&);
narrow_json (*decode)(const bytes&, bool);
};
const std::vector<binary_format> formats =
{
{"CBOR", encode_cbor, decode_cbor},
{"MessagePack", encode_msgpack, decode_msgpack},
{"UBJSON", encode_ubjson, decode_ubjson},
{"BJData", encode_bjdata, decode_bjdata},
{"BSON", encode_bson, decode_bson},
{"BON8", encode_bon8, decode_bon8},
};
for (const auto& format : formats)
{
const std::string name = format.name;
INFO("format := ", name);
const auto roundtrip = [&format](const json & j)
{
return format.decode(format.encode(j), true);
};
// integers that fit keep their type
CHECK(roundtrip(json(-5)).is_number_integer());
CHECK(roundtrip(json(-5)).get<std::int32_t>() == -5);
CHECK(roundtrip(json(3000000000u)).is_number_unsigned());
CHECK(roundtrip(json(3000000000u)).get<std::uint32_t>() == 3000000000u);
// integers that fit neither integer type are stored as float
CHECK(roundtrip(json(5000000000u)).is_number_float());
CHECK(roundtrip(json(5000000000u)).get<float>() == 5000000000.0f);
if (name != "BON8") // BON8 cannot encode integers above INT64_MAX
{
CHECK(roundtrip(json(10000000000000000000u)).is_number_float());
CHECK(roundtrip(json(10000000000000000000u)).get<float>() == 10000000000000000000.0f);
}
CHECK(roundtrip(json(-3000000000LL)).is_number_float());
CHECK(roundtrip(json(-3000000000LL)).get<float>() == -3000000000.0f);
CHECK(roundtrip(json(-5000000000LL)).is_number_float());
CHECK(roundtrip(json(-5000000000LL)).get<float>() == -5000000000.0f);
// floating-point numbers that fit
CHECK(roundtrip(json(1.5)).get<float>() == 1.5f);
const auto just_above_max = std::nextafter(static_cast<double>((std::numeric_limits<float>::max)()),
std::numeric_limits<double>::infinity());
CHECK(roundtrip(json(just_above_max)).get<float>() == (std::numeric_limits<float>::max)());
// infinity and NaN are passed on
CHECK(std::isinf(roundtrip(json(std::numeric_limits<double>::infinity())).get<float>()));
CHECK(std::isnan(roundtrip(json(std::numeric_limits<double>::quiet_NaN())).get<float>()));
// finite floating-point numbers that overflow number_float_t are rejected
const std::string message = "[json.exception.out_of_range.406] syntax error while parsing " + name
+ " value: number overflow";
CHECK_THROWS_WITH_AS(roundtrip(json(1e300)), message.c_str(), narrow_json::out_of_range&);
CHECK_THROWS_WITH_AS(roundtrip(json(-1e300)), message.c_str(), narrow_json::out_of_range&);
CHECK(format.decode(format.encode(json(1e300)), false).is_discarded());
}
}
+3 -90
View File
@@ -9,14 +9,6 @@
#include "doctest_compatibility.h"
#define JSON_TESTS_PRIVATE
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
// line *before* including json.hpp, since the library #undefs it once the header
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
// tests of deprecated functions are skipped if these functions are deleted
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
#endif
#include <nlohmann/json.hpp>
using nlohmann::json;
@@ -114,59 +106,10 @@ TEST_CASE("BJData")
{
SECTION("discarded")
{
// a discarded value cannot be serialized to BJData
// discarded values are not serialized
json const j = json::value_t::discarded;
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
}
SECTION("discarded values nested in a container")
{
json const discarded = json::value_t::discarded;
SECTION("in an array")
{
json const j = {1, discarded, 2};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/1) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("as an object value")
{
json j;
j["a"] = 1;
j["b"] = discarded;
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/b) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("nested deeper (array in object in array)")
{
json inner_array = {1, discarded};
json middle_object;
middle_object["x"] = inner_array;
json const j = {middle_object};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] (/0/x/1) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
SECTION("optimized array of all-discarded elements")
{
json const j = {discarded, discarded};
#if JSON_DIAGNOSTICS
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] (/0) cannot serialize discarded value to BJData", json::type_error&);
#else
CHECK_THROWS_WITH_AS(json::to_bjdata(j, true, true), "[json.exception.type_error.321] cannot serialize discarded value to BJData", json::type_error&);
#endif
}
const auto result = json::to_bjdata(j);
CHECK(result.empty());
}
SECTION("null")
@@ -4547,33 +4490,3 @@ TEST_CASE("BJData roundtrips" * doctest::skip())
}
}
}
TEST_CASE("issue #5648 - from_bjdata(ptr, len) must read len bytes, not treat ptr as a C string")
{
// to_bjdata() encodes the integer 0 as the two bytes 'i' 0x00 (a BJData
// type marker followed by the value byte 0x00), so the packed data
// below contains a 0x00 byte before its end.
const json j = {{"a", 0}};
const std::vector<std::uint8_t> packed = json::to_bjdata(j);
bool contains_nul = false;
for (const auto byte : packed)
{
contains_nul |= (byte == 0x00);
}
REQUIRE(contains_nul);
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
// before the fix, from_bjdata had no (ptr, len) overload, so this call
// bound to from_bjdata(InputType&&, bool strict) instead: ptr was read
// as a NUL-terminated C string (stopping at the embedded 0x00 byte), and
// len was silently converted to the strict flag. The deprecated
// overload added for this issue forwards to from_bjdata(ptr, ptr + len,
// ...) instead, like from_ubjson's deprecated (ptr, len) overload does.
json result;
CHECK_NOTHROW(result = json::from_bjdata(packed.data(), packed.size()));
CHECK(result == j);
// len must not collapse into the strict flag either
CHECK(json::from_bjdata(packed.data(), packed.size(), false) == j);
#endif
}
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more