Compare commits

..
Author SHA1 Message Date
Niels Lohmann 12fcf8ff8c docs: integers are converted while parsing a document
Also say "They accept every input" in basic_json_document::parse.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-24 07:14:37 +02:00
Niels Lohmann e19efec1d3 docs: clarify document inputs, decoding, get<T>() and editing
Address review comments: say which inputs are borrowed and that a wide
string pointer is an extra input, which values are decoded during parsing,
that get<T>() reads only built-in types directly, all forms of set() and
that the document's insert() is array-only, and how to store wide strings.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-23 06:43:55 +02:00
Niels Lohmann 5464ccab0e docs: fix article before std::istream in FAQ
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 19:37:28 +02:00
Niels Lohmann 0a4c1a2d42 docs: cross-reference json_view and json_document for 3.13.0
Document the new zero-copy document/view API across the docs: add a
"Documents and views" section to the basic_json API hub linking to all
specializations, point parsing/conversion/modifying pages to the view-based
alternatives, add document/view sections to feature pages, update the
landing page to mention json_view.hpp/json_literals.hpp, and cross-link
debugging/exceptions.

Also document wide-string inputs (and ill-formed UTF-16/32 rejection),
add parse/accept examples for wide strings, fix related doc gaps, and
align features/parsing and FAQ with the added support.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-22 14:51:05 +02:00
84 changed files with 5585 additions and 14908 deletions

No files matched your search

+3 -3
View File
@@ -38,14 +38,14 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: c-cpp
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
@@ -37,14 +37,6 @@ jobs:
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr"
})[0];
// a run that failed before its jobs started (e.g. a stale queued
// run cancelled long after its push) has no artifact to report on
if (!matchArtifact) {
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
core.setOutput('found', 'false');
return;
}
core.setOutput('found', 'true');
var download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -59,11 +51,9 @@ jobs:
# Extract the untrusted PR artifact into a dedicated empty directory and
# read only the two expected files by fixed path afterwards. This avoids a
# malicious archive overwriting workspace files or escaping via ../ paths.
- if: steps.download.outputs.found == 'true'
run: unzip -o pr.zip -d ./pr_artifact
- run: unzip -o pr.zip -d ./pr_artifact
- name: 'Comment on PR'
if: steps.download.outputs.found == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -47,6 +47,6 @@ jobs:
output: 'flawfinder_results.sarif'
- name: Upload analysis results to GitHub Security tab
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+2 -2
View File
@@ -54,7 +54,7 @@ jobs:
timeout-minutes: 60
steps:
- name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
@@ -78,7 +78,7 @@ jobs:
- name: Summary
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
path: tests/benchmarks/json_view/results/
+1 -1
View File
@@ -80,6 +80,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: results.sarif
+1 -1
View File
@@ -65,7 +65,7 @@ jobs:
# Upload SARIF file generated in previous step
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: semgrep.sarif
if: always()
+1 -2
View File
@@ -341,14 +341,13 @@ def download(url, docs) -> str:
raise ValueError(f'not an http(s) URL: {url}')
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
# (the scheme is checked above)
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
data = r.read()
ctype = r.headers.get_content_type()
path = urllib.parse.unquote(u.path).lstrip('/')
ext = os.path.splitext(path)[1]
if u.query or not ext or path.endswith('/'):
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
out = os.path.join(docs, rel)
+25 -1
View File
@@ -23,6 +23,9 @@ Checks whether the input is valid JSON.
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all make this function return `false`.
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
(i.e., a parse error) nor creates diagnostic information.
@@ -35,9 +38,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a `#!c FILE` pointer (throws if null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string`
- a `std::string` or a `std::string_view` (C++17)
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type, for instance.
@@ -125,6 +131,21 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
--8<-- "examples/accept__iterator_pair.output"
```
??? example "Example: (3) reading from UTF-16 strings"
The encoding is deduced from the character type of the input. String literals like `u"..."`, `U"..."`, and
`L"..."` work as well.
```cpp
--8<-- "examples/accept__wide_string.cpp"
```
Output:
```json
--8<-- "examples/accept__wide_string.output"
```
## See also
- [parse](parse.md) - deserialize from a compatible input
@@ -151,6 +172,9 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected in version 3.13.0; before that, some such sequences were accepted or
ended the input silently.
!!! warning "Deprecation"
+19
View File
@@ -44,6 +44,22 @@ are collected in [Template Parameter Requirements](../../features/types/template
- [**json**](../json.md) - default specialization
- [**ordered_json**](../ordered_json.md) - a specialization that maintains the insertion order of object keys
## Documents and views
Besides `basic_json`, the library offers a second way to hold a parsed JSON text: a
[`basic_json_document`](../basic_json_document/index.md), which owns or borrows the text and holds a flat index of the
values in it, and a [`basic_json_view`](../basic_json_view/index.md), a read-only handle to one value of such a document.
Both are defined in the header `<nlohmann/json_view.hpp>`; see
[Zero-copy JSON views](../../features/json_view.md) for the concept and the trade-offs.
- [**json_document**](../json_document.md) / [**ordered_json_document**](../ordered_json_document.md) - read-only
documents of the `basic_json` specialization they belong to
- [**json_editable_document**](../json_editable_document.md) /
[**ordered_json_editable_document**](../ordered_json_editable_document.md) - documents that can be modified in place
- [**json_view**](../json_view.md) / [**ordered_json_view**](../ordered_json_view.md) - views of a document
- [**json_editable_view**](../json_editable_view.md) /
[**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an editable document
## Iterator invalidation
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
@@ -343,6 +359,9 @@ Access to the JSON value
## See also
- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259)
- [basic_json_document](../basic_json_document/index.md) / [basic_json_view](../basic_json_view/index.md) - parse a JSON
text into a flat index and read it through a view
- [Zero-copy JSON views](../../features/json_view.md) - when to use a document and a view instead of a `basic_json`
## Version history
+34 -1
View File
@@ -25,6 +25,10 @@ static basic_json parse(IteratorType first, SentinelType last,
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
## Template parameters
`InputType`
@@ -34,9 +38,12 @@ static basic_json parse(IteratorType first, SentinelType last,
- a `FILE` pointer (throws if null)
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters (throws if null)
- a `std::string`
- a `std::string` or a `std::string_view` (C++17)
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type, for instance.
@@ -112,6 +119,13 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
!!! tip "Parsing into a document instead of a value"
[`basic_json_document::parse`](../basic_json_document/parse.md) accepts the same inputs and the same options, but
keeps the result as a flat index of the parsed values instead of a tree of `basic_json` values. It is faster and uses
less memory when only parts of the document are read, and it also accepts a pointer to a null-terminated wide string,
which `parse()` does not. See [Zero-copy JSON views](../../features/json_view.md).
!!! tip "Performance"
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
@@ -250,6 +264,22 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
--8<-- "examples/trailing_commas.output"
```
??? example "Example: parse from UTF-16, UTF-32, and wide strings"
The encoding is deduced from the character type of the input; `std::u16string` and `std::u32string` are interpreted
as UTF-16 and UTF-32, and `std::wstring` uses the platform's `wchar_t` width. String literals like `u"..."`,
`U"..."`, and `L"..."` work as well, as do iterators over wide characters.
```cpp
--8<-- "examples/parse__wide_string.cpp"
```
Output:
```json
--8<-- "examples/parse__wide_string.output"
```
## See also
- [accept](accept.md) - check if the input is valid JSON
@@ -280,6 +310,9 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
time).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
sequences were accepted or ended the input silently.
!!! warning "Deprecation"
@@ -31,6 +31,10 @@ Read from input and generate SAX events
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
the iterator type with `operator!=`.
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
low surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
## Template parameters
@@ -42,8 +46,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
- a `FILE` pointer
- a C-style array of characters
- a pointer to a null-terminated string of single byte characters
- a `std::string` or a `std::string_view` (C++17)
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
`IteratorType`
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
@@ -160,6 +168,9 @@ use the same file with stdio functions, but it must not wait for another thread
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
sequences were accepted or ended the input silently.
!!! warning "Deprecation"
@@ -9,8 +9,8 @@ class basic_json_document;
A parsed JSON text, held as a flat index of its values
([16 bytes per value](../../home/architecture.md#node-index-of-json-views)) instead of a tree of `BasicJsonType` values.
Strings and numbers stay in the source text; only strings that contain escapes are decoded, into one buffer owned by
the document. [`basic_json_view`](../basic_json_view/index.md) is a read-only handle to one value of a
Strings and floating-point numbers stay in the source text; integers are converted while parsing, and strings that
contain escapes are decoded into one buffer owned by the document. [`basic_json_view`](../basic_json_view/index.md) is a read-only handle to one value of a
`basic_json_document`; [`materialize()`](../basic_json_view/materialize.md) turns a subtree into the
`BasicJsonType` value that [`BasicJsonType::parse()`](../basic_json/parse.md) would have produced for it.
@@ -115,6 +115,12 @@ points apply to every edit:
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
`#!cpp static_cast<std::size_t>(-1)`, the same value it reports for a decoded string.
## See also
- [basic_json_view](../basic_json_view/index.md) - the read-only handle to a value of a document
- [json_document](../json_document.md) - this template for the default specialization `nlohmann::json`
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
## Version history
- Added in version 3.13.0.
@@ -21,17 +21,19 @@ static basic_json_document parse(IteratorType first, IteratorType last,
Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same
`ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`. Byte
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an
owned copy of a borrowable input as well.
the input) instead of a tree of `BasicJsonType` values. They accept every input `BasicJsonType::parse()` takes, and in
addition a pointer to a null-terminated wide string (which `BasicJsonType::parse()` rejects). Contiguous inputs of
single-byte characters that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, which
must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns -- see the
Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input
as well.
## Template parameters
`InputType`
: A compatible input, for instance:
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters
- a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), or a C-style array of single-byte characters
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
- a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g.
@@ -47,6 +49,10 @@ owned copy of a borrowable input as well.
`#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs
above, those of wide characters are converted to UTF-8
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its low
surrogate, and a code point above `U+10FFFF` all yield a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
## Parameters
`input` (in)
@@ -155,3 +161,5 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
## Version history
- Added in version 3.13.0.
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
a code point above `U+10FFFF`) is rejected with `parse_error.101`.
+1 -2
View File
@@ -80,8 +80,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
or object, since the elements are then adjacent in the index.
index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.).
+1 -2
View File
@@ -28,8 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
index. Constant if no element of the array is an array or object, since the elements are then adjacent
in the index.
index.
## Notes
@@ -143,6 +143,12 @@ still refers to it -- including ones taken before the change -- reads the new va
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text
## See also
- [basic_json_document](../basic_json_document/index.md) - the document a view refers into
- [materialize](materialize.md) - build the `BasicJsonType` value of a subtree
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
## Version history
- Added in version 3.13.0.
@@ -86,8 +86,7 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
constant time on average.
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
or object, since the elements are then adjacent in the index.
index (unlike `BasicJsonType`'s array, which is random-access).
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
that level (as 1.) or the index into the array (as 2.).
@@ -0,0 +1,18 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// a valid JSON text, given as UTF-16
const std::u16string valid_text = u"{\"name\":\"\u00e9\"}";
// an invalid JSON text, given as UTF-16
const std::u16string invalid_text = u"{\"name\":\"\u00e9\",}";
std::cout << std::boolalpha
<< json::accept(valid_text) << ' '
<< json::accept(invalid_text) << '\n';
}
@@ -0,0 +1 @@
true false
@@ -0,0 +1,27 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// the same JSON text, given in different encodings
const std::string utf8 = "{\"name\":\"\u00e9\"}";
const std::u16string utf16 = u"{\"name\":\"\u00e9\"}";
const std::u32string utf32 = U"{\"name\":\"\u00e9\"}";
const std::wstring wide = L"{\"name\":\"\u00e9\"}";
// the encoding is deduced from the character type of the input, and all
// of them yield the same value
std::cout << json::parse(utf8) << '\n'
<< json::parse(utf16) << '\n'
<< json::parse(utf32) << '\n'
<< json::parse(wide) << '\n';
// a wide string literal works as well: u"..." is UTF-16, U"..." is UTF-32
std::cout << json::parse(u"[1, 2, 3]") << '\n';
// iterators over wide characters work as well
std::cout << json::parse(utf16.begin(), utf16.end()) << '\n';
}
@@ -0,0 +1,6 @@
{"name":"é"}
{"name":"é"}
{"name":"é"}
{"name":"é"}
[1,2,3]
{"name":"é"}
+13
View File
@@ -38,6 +38,18 @@ which avoids repeating it:
--8<-- "examples/get_to.output"
```
!!! info "Getting values out of a document or a view"
A [view](../api/basic_json_view/index.md) offers the same `get<T>()` conversion. For the built-in types (booleans,
numbers, strings, `#!cpp std::vector`, `#!cpp std::map`, ...) it reads the value straight from the document's
index, without building a `basic_json` value first; user-defined types with a `from_json()` are converted from a
`basic_json` value, see the [list](../api/basic_json_view/get.md). Two conversions never copy at all:
[`get_string()`](../api/basic_json_view/get_string.md) returns a string as a view into the source text, and
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
type is obtained from a subtree that is built on demand with
[`materialize()`](../api/basic_json_view/materialize.md). See
[Zero-copy JSON views](json_view.md#getting-values-out-without-copying).
The library already knows how to convert to and from the scalar types and the STL containers (such as
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
@@ -234,3 +246,4 @@ The conversions above are built in for standard types. To make the same syntax w
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
- [Zero-copy JSON views](json_view.md) - get values out of a document or a view
+4 -1
View File
@@ -1,7 +1,9 @@
# Creating JSON values
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
instead, see [parsing](parsing/index.md).
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
into a value; see [Zero-copy JSON views](json_view.md).
## From C++ values
@@ -101,3 +103,4 @@ string `#!json "42"`.
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
- [Converting values](conversions.md) - which C++ types can be used
- [Parsing](parsing/index.md) - create a value from JSON text
- [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value
+17
View File
@@ -287,6 +287,23 @@ external dependency and is not defined by default.
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
## `JSON_VIEW_NO_SIMD`
When defined, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
(`<nlohmann/json_view.hpp>`) uses only portable C++ to scan strings, instead of vector instructions (SSE2 on x86-64,
NEON on AArch64). The same input is accepted or rejected either way; only the speed differs. It is not defined by
default.
See [full documentation of `JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md).
## `JSON_VIEW_USE_SSSE3`
When defined on x86-64, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3 without asking the CPU first. It is not
defined by default.
See [full documentation of `JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md).
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
@@ -85,6 +85,18 @@ json a = {1, 2, 3, 4};
a.erase(1); // [1,3,4] (erase by index)
```
## Editing a document
The operations above work on a `basic_json` value. A JSON text that was parsed into a
[document](json_view.md#editing-a-document) can also be changed in place, without building a `basic_json` tree for the
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value, adds or replaces an object
member, or replaces an array element (also through a JSON pointer),
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
[`insert`](../api/basic_json_document/insert.md) inserts into an array (unlike `basic_json::insert`, it has no object
overload; members are added with `set`), and [`erase`](../api/basic_json_document/erase.md) removes a member or an
element. These functions are available on the
editable document types, such as [`json_editable_document`](../api/json_editable_document.md).
## See also
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
@@ -93,3 +105,4 @@ a.erase(1); // [1,3,4] (erase by index)
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications
- [Editing a document](json_view.md#editing-a-document) - change a parsed document in place
+30 -1
View File
@@ -18,6 +18,7 @@ flowchart LR
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
- a string (`#!cpp std::string`, C string, or string literal),
- a wide string (`#!cpp std::u16string`, `#!cpp std::u32string`, or `#!cpp std::wstring`, or a wide string literal),
- a `#!cpp std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
- a `#!cpp FILE*` pointer,
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
@@ -32,12 +33,18 @@ std::ifstream f("example.json");
json data = json::parse(f);
```
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
is read as UTF-16 or UTF-32 -- depending on the character type -- and converted to UTF-8; see the
[`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value.
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
[`basic_json_document::parse`](../../api/basic_json_document/parse.md) accepts the same inputs with the same options,
but keeps the result in a document rather than in a `basic_json` value; see
[Documents and views](#documents-and-views) below.
## Strictness and trailing data
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
@@ -98,6 +105,26 @@ The library offers two parsing models:
You can influence a DOM parse without switching to the SAX interface by passing a
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
## Documents and views
A third model sits between DOM and SAX: [`basic_json_document::parse`](../../api/basic_json_document/parse.md) parses
the input into a document that keeps the text and a flat index of the values in it, and
[`basic_json_view`](../../api/basic_json_view/index.md) gives read-only access to the values through that index.
The whole input is validated during parsing, but only integers and strings that contain escape sequences are converted
right away (the strings into a buffer owned by the document). Floating-point numbers and all other strings stay in the
source text and are only converted when they are read, which is faster and uses less memory than building a tree of `basic_json` values when only parts
of the document are needed. A document can also be
modified in place, and it can be saved and loaded again without parsing it.
```cpp
#include <nlohmann/json_view.hpp>
json_document doc = json_document::parse(R"({"happy": true, "pi": 3.141})");
std::cout << doc.root()["happy"].get<bool>() << '\n';
```
See [Zero-copy JSON views](../json_view.md) for the concept and the trade-offs.
## Exceptions
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
@@ -108,6 +135,7 @@ options.
## See also
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
- [`basic_json_document::parse`](../../api/basic_json_document/parse.md) - deserialize into a document instead of a value
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
@@ -115,3 +143,4 @@ options.
- [SAX interface](sax_interface.md) - implement a custom SAX handler
- [parsing and exceptions](parse_exceptions.md) - control error handling
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
- [Zero-copy JSON views](../json_view.md) - parse into a document and read it through a view
@@ -81,6 +81,9 @@ Invalid UTF-8 is rejected while parsing, not just while serializing:
- In JSON text, an ill-formed UTF-8 byte in a string is a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) ("invalid string: ill-formed UTF-8 byte").
- In UTF-16 or UTF-32 input, an ill-formed sequence is a
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) as well: a lone surrogate, a high surrogate
that is not followed by its low surrogate, and a code point above `U+10FFFF` are all rejected.
- In a binary format, a string that is not valid UTF-8 is a
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
-5
View File
@@ -339,11 +339,6 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
- `output_stream_adapter` writes to a `std::ostream`.
- `output_string_adapter` appends to a string.
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
## Value conversion
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
+2 -2
View File
@@ -1,7 +1,7 @@
# Debugging
This page collects the library's built-in debugger integrations and other debugging-related features. They are
not linked from a single place elsewhere in the docs, so are collected here.
This page collects the library's built-in debugger integrations and other debugging-related features. They are not
covered by any single page elsewhere in the docs, so they are collected here.
## Visual Studio (natvis)
+2 -1
View File
@@ -99,7 +99,8 @@ As this global context comes at the price of storing one additional pointer per
Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information, and
[Debugging](debugging.md) for the debugger integrations of the library.
## Parse errors
+4 -3
View File
@@ -81,8 +81,9 @@ rejected by default, and the library never adds them when serializing.
The library supports **Unicode input** as follows:
- Only **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers.
- **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively; `std::wstring` works as well and is interpreted as UTF-16 or UTF-32, depending on the width of the platform's `wchar_t`. These encodings cannot be read from a `FILE*` or a `std::istream`, which carry bytes only.
- Ill-formed UTF-16 or UTF-32 input is rejected: a lone surrogate, a high surrogate that is not followed by its low surrogate, and a code point above `U+10FFFF` all yield a parse error. The same holds for iterators over 2- or 4-byte characters.
- Other encodings such as Latin-1 or ISO 8859-1 are **not** supported and will yield parse or serialization errors.
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
@@ -146,7 +147,7 @@ json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
As described [above](#parse-errors-reading-non-ascii-characters), the library assumes UTF-8 as encoding. To store a wide string, you need to change the encoding.
As described [above](#parse-errors-reading-non-ascii-characters), the library stores its strings as UTF-8. To store a wide string as a string, convert it to a UTF-8 encoded `std::string` first, as in the example below.
!!! example
+7 -3
View File
@@ -6,7 +6,9 @@ JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-
magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like
Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no
complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
down. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
`<nlohmann/json_view.hpp>` adds zero-copy views and editable documents. See
[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
OSS-Fuzz.
@@ -58,7 +60,8 @@ Conda, and more), and pkg-config.
---
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more.
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
and more.
[:octicons-arrow-right-24: Features](features/index.md)
@@ -74,7 +77,8 @@ Conda, and more), and pkg-config.
---
The complete reference for `basic_json` and its member functions, types, and related classes.
The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
`json_document` and `json_view`.
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
+15 -15
View File
@@ -6,7 +6,7 @@
"": {
"name": "check-mermaid",
"dependencies": {
"jsdom": "30.1.3",
"jsdom": "30.1.2",
"mermaid": "11.17.2"
}
},
@@ -40,9 +40,9 @@
}
},
"node_modules/@asamuzakjp/dom-selector": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
"version": "9.2.4",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
"license": "MIT",
"dependencies": {
"bidi-js": "^1.1.0",
@@ -1105,9 +1105,9 @@
}
},
"node_modules/dompurify": {
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
"version": "3.4.16",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
@@ -1202,13 +1202,13 @@
"license": "MIT"
},
"node_modules/jsdom": {
"version": "30.1.3",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
"version": "30.1.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
"license": "MIT",
"dependencies": {
"@asamuzakjp/css-color": "^7.1.3",
"@asamuzakjp/dom-selector": "^10.0.3",
"@asamuzakjp/dom-selector": "^9.2.4",
"@bramus/specificity": "^2.4.2",
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
"@exodus/bytes": "^1.16.0",
@@ -1221,7 +1221,7 @@
"parse5": "^8.0.1",
"saxes": "^6.0.0",
"tough-cookie": "^6.0.2",
"undici": "^8.12.1",
"undici": "^8.11.2",
"w3c-xmlserializer": "^6.0.0",
"webidl-conversions": "^8.0.1",
"whatwg-mimetype": "^5.0.0",
@@ -1521,9 +1521,9 @@
}
},
"node_modules/undici": {
"version": "8.12.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
"license": "MIT",
"engines": {
"node": ">=22.19.0"
+1 -1
View File
@@ -4,7 +4,7 @@
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
"type": "module",
"dependencies": {
"jsdom": "30.1.3",
"jsdom": "30.1.2",
"mermaid": "11.17.2"
}
}
@@ -69,10 +69,12 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
}
#endif // JSON_HAS_CPP_17
// read a number value of any of the three number types into val;
// returns false (leaving val untouched) if j is not a number
template<typename BasicJsonType, typename ArithmeticType>
bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
// overloads for basic_json template parameters
template < typename BasicJsonType, typename ArithmeticType,
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
switch (static_cast<value_t>(j))
{
@@ -83,17 +85,17 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
JSON_ASSERT(ptr != nullptr);
val = static_cast<ArithmeticType>(*ptr);
return true;
break;
}
case value_t::number_integer:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
return true;
break;
}
case value_t::number_float:
{
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
return true;
break;
}
case value_t::null:
@@ -104,20 +106,7 @@ bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
case value_t::binary:
case value_t::discarded:
default:
return false;
}
}
// overloads for basic_json template parameters
template < typename BasicJsonType, typename ArithmeticType,
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
int > = 0 >
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
{
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
{
throw_type_must_be("number", j);
throw_type_must_be("number", j);
}
}
@@ -267,18 +256,18 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
}
}
// reserve() is called through this pair (used for arrays and objects alike)
// so from_json_array_impl and the object from_json have a single body for both
// containers that support reserve() and those that don't.
template<typename ConstructibleContainerType>
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/)
-> decltype(c.reserve(size), void())
// reserve() is called through this pair (modeled on from_json_object_reserve)
// so from_json_array_impl below has a single body for both ConstructibleArrayType
// that support reserve() and those that don't.
template<typename ConstructibleArrayType>
auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
-> decltype(arr.reserve(size), void())
{
c.reserve(size);
arr.reserve(size);
}
template<typename ConstructibleContainerType>
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
template<typename ConstructibleArrayType>
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// append through push_back() where available; containers without it (such as
@@ -308,7 +297,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
{
ConstructibleArrayType ret;
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {});
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
for (const auto& elem : *inner_array)
{
// get<BasicJsonType>() returns *this, this won't call a from_json
@@ -389,6 +378,17 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
}
}
template<typename ConstructibleObjectType>
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
-> decltype(obj.reserve(size), void())
{
obj.reserve(size);
}
template<typename ConstructibleObjectType>
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
{}
// the keys of the source are sorted for the usual object types, so hinting the
// end makes every insertion constant-time (for containers that order their keys
// the same way); containers without emplace_hint() (such as ordered_map) fall
@@ -417,7 +417,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
ConstructibleObjectType ret;
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {});
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
for (const auto& p : *inner_object)
{
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
@@ -437,10 +437,6 @@ template < typename BasicJsonType, typename ArithmeticType,
int > = 0 >
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
{
// the number cases repeat get_number_value() on purpose: calling it here
// changes the inlining of get<T>() in the std::array conversion, which then
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
// a boolean is accepted
switch (static_cast<value_t>(j))
{
case value_t::number_unsigned:
+36 -32
View File
@@ -66,7 +66,6 @@ enum class exception_id : int
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
bson_unsupported_type = 114, ///< a BSON record type is not supported
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
invalid_document_image = 116, ///< json_document::load() rejects an image
// invalid_iterator
iterators_incompatible = 201, ///< the iterators of a range belong to different values
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
@@ -101,8 +100,6 @@ enum class exception_id : int
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
document_binary_value = 319, ///< a binary value is stored in a json_document
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
discarded_value_used = 321, ///< a discarded value is used
// out_of_range
array_index_out_of_range = 401, ///< an array index is out of range
@@ -120,7 +117,6 @@ enum class exception_id : int
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
// other_error
internal_error = 500, ///< unreachable code was reached
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
@@ -150,13 +146,6 @@ class exception : public std::exception
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
}
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
template<typename BasicJsonContext>
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
{
return concat(name(ename, id_), diagnostics(context), what_arg);
}
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
{
return "";
@@ -169,33 +158,48 @@ class exception : public std::exception
std::vector<std::string> tokens;
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
{
const auto* parent = current->m_parent;
if (parent->is_array())
switch (current->m_parent->type())
{
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i)
case value_t::array:
{
if (&parent->m_data.m_value.array->operator[](i) == current)
for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
{
tokens.emplace_back(std::to_string(i));
break;
if (&current->m_parent->m_data.m_value.array->operator[](i) == current)
{
tokens.emplace_back(std::to_string(i));
break;
}
}
break;
}
}
else if (parent->is_object())
{
for (const auto& element : *parent->m_data.m_value.object)
case value_t::object:
{
if (&element.second == current)
for (const auto& element : *current->m_parent->m_data.m_value.object)
{
// data() is null-terminated, so a key containing
// a null byte is cut short here rather than
// truncating the whole message at what()
tokens.emplace_back(element.first.data());
break;
if (&element.second == current)
{
// data() is null-terminated, so a key containing
// a null byte is cut short here rather than
// truncating the whole message at what()
tokens.emplace_back(element.first.data());
break;
}
}
break;
}
case value_t::null: // LCOV_EXCL_LINE
case value_t::string: // LCOV_EXCL_LINE
case value_t::boolean: // LCOV_EXCL_LINE
case value_t::number_integer: // LCOV_EXCL_LINE
case value_t::number_unsigned: // LCOV_EXCL_LINE
case value_t::number_float: // LCOV_EXCL_LINE
case value_t::binary: // LCOV_EXCL_LINE
case value_t::discarded: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
break; // LCOV_EXCL_LINE
}
// all other parent types (primitives) contribute no token
}
if (tokens.empty())
@@ -311,7 +315,7 @@ class invalid_iterator : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
{
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context);
const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()};
}
@@ -335,7 +339,7 @@ class type_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{
const std::string w = exception::what_impl("type_error", id_, what_arg, context);
const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()};
}
@@ -358,7 +362,7 @@ class out_of_range : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
{
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context);
const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()};
}
@@ -381,7 +385,7 @@ class other_error : public exception
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
{
const std::string w = exception::what_impl("other_error", id_, what_arg, context);
const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
return {id_, w.c_str()};
}
+83 -109
View File
@@ -86,7 +86,7 @@ JSON_INLINE_VARIABLE constexpr std::size_t max_valueless_container_size = 1 << 2
/*!
@brief deserialization of BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
*/
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_binary_parser<BasicJsonType, InputAdapterType>>
template<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>
class binary_reader
{
using number_integer_t = typename BasicJsonType::number_integer_t;
@@ -293,40 +293,6 @@ class binary_reader
return is_object ? sax->end_object() : sax->end_array();
}
/*!
@brief close the innermost open container and report whether that
completed the value being read
The value begun by the driver is complete once its container is not
inside another one.
@param[out] value_complete whether no container is left open
@return whether the SAX parser accepted the end event
*/
bool leave_container_and_check(bool& value_complete)
{
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
value_complete = container_stack.empty();
return true;
}
/*!
@brief read an object key and pass it to the SAX parser
@tparam GetKey member function that reads the key into a string
@param[in,out] key string the key is read into; cleared first
@return whether reading the key and the SAX event succeeded
*/
template<bool (binary_reader::*GetKey)(string_t&)>
bool read_object_key(string_t& key)
{
key.clear();
return (this->*GetKey)(key) && sax->key(key);
}
//////////
// BSON //
//////////
@@ -1098,34 +1064,6 @@ class binary_reader
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
}
/*!
@brief report an object key that is not a string
@param[in] found description of what was found instead of a string key
@return false
*/
bool unsupported_key_type(const char* found) const
{
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
}
/*!
@brief read the length of a CBOR string or byte array and then its bytes
@tparam NumberType the type of the length field
@tparam ContainerType the destination container (string_t or binary_t)
@param[in] context further context information (for diagnostics)
@param[out] result container the bytes are appended to
@return whether reading completed
*/
template<typename NumberType, typename ContainerType>
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
{
NumberType len{};
return get_number(len) && get_bytes(len, context, result);
}
/*!
@brief reads a definite-length CBOR string
@@ -1176,22 +1114,26 @@ class binary_reader
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
{
return get_cbor_sized_bytes<std::uint8_t>("string", result);
std::uint8_t len{};
return get_number(len) && get_string(len, result);
}
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
{
return get_cbor_sized_bytes<std::uint16_t>("string", result);
std::uint16_t len{};
return get_number(len) && get_string(len, result);
}
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
{
return get_cbor_sized_bytes<std::uint32_t>("string", result);
std::uint32_t len{};
return get_number(len) && get_string(len, result);
}
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
{
return get_cbor_sized_bytes<std::uint64_t>("string", result);
std::uint64_t len{};
return get_number(len) && get_string(len, result);
}
default:
@@ -1326,7 +1268,7 @@ class binary_reader
break;
}
return unsupported_key_type(found);
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
}
/*!
@@ -1379,22 +1321,30 @@ class binary_reader
case 0x58: // Binary data (one-byte uint8_t for n follows)
{
return get_cbor_sized_bytes<std::uint8_t>("binary", result);
std::uint8_t len{};
return get_number(len) &&
get_binary(len, result);
}
case 0x59: // Binary data (two-byte uint16_t for n follow)
{
return get_cbor_sized_bytes<std::uint16_t>("binary", result);
std::uint16_t len{};
return get_number(len) &&
get_binary(len, result);
}
case 0x5A: // Binary data (four-byte uint32_t for n follow)
{
return get_cbor_sized_bytes<std::uint32_t>("binary", result);
std::uint32_t len{};
return get_number(len) &&
get_binary(len, result);
}
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
{
return get_cbor_sized_bytes<std::uint64_t>("binary", result);
std::uint64_t len{};
return get_number(len) &&
get_binary(len, result);
}
default:
@@ -1609,12 +1559,12 @@ class binary_reader
if (at_end)
{
bool value_complete = false;
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
if (value_complete)
// the value begun here is complete once its container is
if (container_stack.empty())
{
return true;
}
@@ -1623,7 +1573,8 @@ class binary_reader
if (top.is_object)
{
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
{
return false;
}
@@ -2094,17 +2045,20 @@ class binary_reader
case 0xD9: // str 8
{
return get_string_with_length<std::uint8_t>(result, context);
std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
case 0xDA: // str 16
{
return get_string_with_length<std::uint16_t>(result, context);
std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
case 0xDB: // str 32
{
return get_string_with_length<std::uint32_t>(result, context);
std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
default:
@@ -2201,7 +2155,7 @@ class binary_reader
break;
}
return unsupported_key_type(found);
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
}
/*!
@@ -2346,12 +2300,12 @@ class binary_reader
if (container_stack.back().remaining == 0)
{
bool value_complete = false;
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
if (value_complete)
// the value begun here is complete once its container is
if (container_stack.empty())
{
return true;
}
@@ -2364,7 +2318,8 @@ class binary_reader
if (is_object)
{
get();
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key)))
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
{
return false;
}
@@ -2548,7 +2503,8 @@ class binary_reader
{
case 'U':
{
return get_string_with_length<std::uint8_t>(result, context);
std::uint8_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
case 'i':
@@ -2581,7 +2537,8 @@ class binary_reader
{
break;
}
return get_string_with_length<std::uint16_t>(result, context);
std::uint16_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
case 'm':
@@ -2590,7 +2547,8 @@ class binary_reader
{
break;
}
return get_string_with_length<std::uint32_t>(result, context);
std::uint32_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
case 'M':
@@ -2599,7 +2557,8 @@ class binary_reader
{
break;
}
return get_string_with_length<std::uint64_t>(result, context);
std::uint64_t len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
default:
@@ -3415,12 +3374,12 @@ class binary_reader
if (at_end)
{
bool value_complete = false;
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
{
return false;
}
if (value_complete)
// the value begun here is complete once its container is
if (container_stack.empty())
{
return true;
}
@@ -3429,7 +3388,8 @@ class binary_reader
if (top.is_object)
{
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key)))
key.clear();
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
{
return false;
}
@@ -4169,22 +4129,6 @@ class binary_reader
return true;
}
/*!
@brief read a length of type @a NumberType, then a string of that many bytes
@tparam NumberType the type of the length field
@param[out] result string the bytes are appended to
@param[in] context further context information (for diagnostics)
@return whether reading and validating the string succeeded
*/
template<typename NumberType>
bool get_string_with_length(string_t& result, const char* context)
{
NumberType len{};
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
}
/*!
@brief create a byte array by reading bytes from the input
@@ -4394,7 +4338,37 @@ class binary_reader
const std::string& context) const
{
std::string error_msg = "syntax error while parsing ";
error_msg += input_format_name(input_format);
switch (input_format)
{
case input_format_t::cbor:
error_msg += "CBOR";
break;
case input_format_t::msgpack:
error_msg += "MessagePack";
break;
case input_format_t::ubjson:
error_msg += "UBJSON";
break;
case input_format_t::bson:
error_msg += "BSON";
break;
case input_format_t::bjdata:
error_msg += "BJData";
break;
case input_format_t::bon8:
error_msg += "BON8";
break;
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
}
return concat(error_msg, ' ', context, ": ", detail);
}
+36 -225
View File
@@ -8,18 +8,17 @@
#pragma once
#include <algorithm> // count, find, max, min
#include <algorithm> // count, find, min
#include <array> // array
#include <cstddef> // size_t
#include <cstdint> // uint32_t
#include <cstring> // strlen
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
#include <limits> // numeric_limits
#include <memory> // unique_ptr
#include <streambuf> // streambuf
#include <string> // string, char_traits
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
#include <utility> // declval, move, pair
#include <utility> // pair, declval
#include <vector> // vector
#ifndef JSON_NO_IO
@@ -67,118 +66,12 @@ namespace detail
/// the supported input formats
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
/// @return the name of a binary input format, as used in exception messages
inline const char* input_format_name(const input_format_t format) noexcept
{
switch (format)
{
case input_format_t::cbor:
return "CBOR";
case input_format_t::msgpack:
return "MessagePack";
case input_format_t::ubjson:
return "UBJSON";
case input_format_t::bson:
return "BSON";
case input_format_t::bjdata:
return "BJData";
case input_format_t::bon8:
return "BON8";
case input_format_t::json: // LCOV_EXCL_LINE
default: // LCOV_EXCL_LINE
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
return ""; // LCOV_EXCL_LINE
}
}
/// @return the message of the parse_error thrown or reported for an empty input
inline const char* empty_input_message() noexcept
{
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
}
////////////////////
// input adapters //
////////////////////
#ifndef JSON_NO_IO
/*!
A growable array of characters for the part of a token that an input adapter
copied out of its window: unlike std::vector<char>::push_back(), appending a
character takes no reference to a local, and is a few instructions when there
is room.
*/
class token_carry_buffer
{
public:
token_carry_buffer() = default;
token_carry_buffer(const token_carry_buffer&) = delete;
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
token_carry_buffer(token_carry_buffer&& rhs) noexcept
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
{
rhs.m_end = rhs.m_capacity_end = nullptr;
}
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
~token_carry_buffer() = default;
void clear() noexcept
{
m_end = m_data.get();
}
void push_back(char c)
{
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
{
grow(1);
}
*m_end++ = c;
}
void append(const char* first, const char* last)
{
const auto n = static_cast<std::size_t>(last - first);
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
if (n > room)
{
grow(n);
}
if (n != 0)
{
std::memcpy(m_end, first, n);
}
m_end += n;
}
/// replace the contents of @a out
void copy_to(std::vector<char>& out) const
{
out.assign(m_data.get(), m_end);
}
private:
/// make room for @a extra more characters
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
{
const auto size = static_cast<std::size_t>(m_end - m_data.get());
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
if (size != 0)
{
std::memcpy(data.get(), m_data.get(), size);
}
m_data = std::move(data);
m_end = m_data.get() + size;
m_capacity_end = m_data.get() + new_capacity;
}
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
char* m_end = nullptr;
char* m_capacity_end = nullptr;
};
/*!
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
buffer. This adapter is a very low level adapter.
*/
@@ -316,7 +209,6 @@ class input_stream_adapter
, token_segment(rhs.token_segment)
, current_in_get_area(rhs.current_in_get_area)
, tracking_token(rhs.tracking_token)
, no_get_area(rhs.no_get_area)
#endif
{
rhs.is = nullptr;
@@ -403,20 +295,37 @@ class input_stream_adapter
std::char_traits<char>::int_type get_character()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the character is in the get area: step over it, as sbumpc() does
const char* const next = streambuf_access::get_ptr(sb);
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
current_in_get_area = true;
streambuf_access::bump(sb, 1);
return std::char_traits<char>::to_int_type(*next);
}
if (no_get_area)
{
return get_character_no_get_area();
save_token_segment();
}
#endif
return get_character_slow();
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
@@ -425,18 +334,6 @@ class input_stream_adapter
{
#if JSON_PRECISE_STREAM_POSITION
commit_lookahead();
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// the elements are in the get area: copy them, as sgetn() does, but
// without the virtual call
const char* const first = streambuf_access::get_ptr(sb);
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
{
std::memcpy(dest, first, count * sizeof(T));
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
return count * sizeof(T);
}
#endif
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
@@ -524,7 +421,7 @@ class input_stream_adapter
// last one if @a drop_last.
void copy_token(std::vector<char>& out, bool drop_last) const
{
token_carry.copy_to(out);
out = token_carry;
if (token_segment != nullptr)
{
out.insert(out.end(), token_segment, current_end());
@@ -537,47 +434,6 @@ class input_stream_adapter
#endif
private:
#if !JSON_PRECISE_STREAM_POSITION
// the get area is exhausted, or there is none
#if !JSON_DISABLE_STREAM_BULK_SCAN
JSON_HEDLEY_NEVER_INLINE
#endif
std::char_traits<char>::int_type get_character_slow()
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
// sbumpc() refills the get area if it is exhausted
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_UNLIKELY(refill))
{
save_token_segment();
}
#endif
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
}
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
// Reads the protected get-area pointers of any std::streambuf. Naming the
// members through a derived class makes forming the member pointers
@@ -601,11 +457,6 @@ class input_stream_adapter
{
(s->*(&streambuf_access::gbump))(n);
}
// sbumpc() for an exhausted get area
static std::streambuf::int_type next_character(std::streambuf* s)
{
return (s->*(&streambuf_access::uflow))();
}
};
// one past the character last returned by get_character(), if that is in
@@ -626,53 +477,16 @@ class input_stream_adapter
{
if (token_segment != nullptr)
{
token_carry.append(token_segment, streambuf_access::get_end(sb));
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
token_segment = nullptr;
}
}
#if !JSON_PRECISE_STREAM_POSITION
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
// every character comes from uflow(), and the token is kept by value.
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
{
// the get area is exhausted, so this is what sbumpc() does
const auto res = streambuf_access::next_character(sb);
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
{
return get_character_no_get_area_other(res);
}
// current_in_get_area is false: only the first character of a get
// area, or the end of the input, is read here
if (tracking_token)
{
token_carry.push_back(std::char_traits<char>::to_char_type(res));
}
return res;
}
// @a res came from uflow() of a streambuf without a get area: it is the
// end of the input, or the streambuf has a get area now
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
{
if (res == std::char_traits<char>::eof())
{
current_in_get_area = false;
is->clear(is->rdstate() | std::ios::eofbit);
return res;
}
// the consumed character is at gptr() - 1
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
return res;
}
#endif
// character @a c was just read into a new get area (@a in_get_area) or
// returned without one
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
{
current_in_get_area = in_get_area;
no_get_area = !in_get_area;
if (!tracking_token)
{
return;
@@ -711,16 +525,13 @@ class input_stream_adapter
#endif
#if !JSON_DISABLE_STREAM_BULK_SCAN
/// the part of the current token that was in earlier get areas
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr
const char* token_segment = nullptr;
/// whether the character last returned by get_character() is in the get area
bool current_in_get_area = false;
/// whether a token is tracked at all; the binary readers never start one
bool tracking_token = false;
/// whether the streambuf had no get area when it was last asked for a
/// character
bool no_get_area = false;
#endif
};
#endif // JSON_NO_IO
@@ -1298,7 +1109,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
{
if (file == nullptr)
{
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
}
return file_input_adapter(file);
}
@@ -1307,7 +1118,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
{
if (stream.rdbuf() == nullptr)
{
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
}
return input_stream_adapter(stream);
}
@@ -1332,7 +1143,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
{
if (b == nullptr)
{
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
}
auto length = std::strlen(reinterpret_cast<const char*>(b));
const auto* ptr = reinterpret_cast<const char*>(b);
File diff suppressed because it is too large. Load diff
+156 -46
View File
@@ -14,7 +14,6 @@
#include <cstdio> // snprintf
#include <cstring> // memcmp
#include <initializer_list> // initializer_list
#include <limits> // numeric_limits
#include <string> // char_traits, string
#include <utility> // move
#include <vector> // vector
@@ -657,74 +656,194 @@ class lexer : public lexer_base<BasicJsonType>
// invalid control characters
case 0x00:
{
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
return token_type::parse_error;
}
case 0x01:
{
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
return token_type::parse_error;
}
case 0x02:
{
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
return token_type::parse_error;
}
case 0x03:
{
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
return token_type::parse_error;
}
case 0x04:
{
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
return token_type::parse_error;
}
case 0x05:
{
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
return token_type::parse_error;
}
case 0x06:
{
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
return token_type::parse_error;
}
case 0x07:
{
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
return token_type::parse_error;
}
case 0x08:
{
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
return token_type::parse_error;
}
case 0x09:
{
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
return token_type::parse_error;
}
case 0x0A:
{
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
return token_type::parse_error;
}
case 0x0B:
{
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
return token_type::parse_error;
}
case 0x0C:
{
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
return token_type::parse_error;
}
case 0x0D:
{
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
return token_type::parse_error;
}
case 0x0E:
{
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
return token_type::parse_error;
}
case 0x0F:
{
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
return token_type::parse_error;
}
case 0x10:
{
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
return token_type::parse_error;
}
case 0x11:
{
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
return token_type::parse_error;
}
case 0x12:
{
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
return token_type::parse_error;
}
case 0x13:
{
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
return token_type::parse_error;
}
case 0x14:
{
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
return token_type::parse_error;
}
case 0x15:
{
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
return token_type::parse_error;
}
case 0x16:
{
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
return token_type::parse_error;
}
case 0x17:
{
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
return token_type::parse_error;
}
case 0x18:
{
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
return token_type::parse_error;
}
case 0x19:
{
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
return token_type::parse_error;
}
case 0x1A:
{
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
return token_type::parse_error;
}
case 0x1B:
{
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
return token_type::parse_error;
}
case 0x1C:
{
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
return token_type::parse_error;
}
case 0x1D:
{
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
return token_type::parse_error;
}
case 0x1E:
{
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
return token_type::parse_error;
}
case 0x1F:
{
static const std::array<const char*, 32> control_character_messages = {{
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
"invalid string: control character U+001F (US) must be escaped to \\u001F"
}
};
error_message = control_character_messages[static_cast<std::size_t>(current)];
error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
return token_type::parse_error;
}
@@ -1697,15 +1816,6 @@ scan_number_done:
/// significand (see scan_number_bulk_contiguous())
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
{
// accept() only needs to know that the value is finite (see the parser):
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
// value that underflows is a finite number as well. Only a token that
// may overflow has to be converted to find out.
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
{
value_float = 0;
return true;
}
value_float = decimal_to_float<number_float_t>(s);
return true;
}
+35 -49
View File
@@ -56,8 +56,8 @@ using parser_callback_t =
template<typename SAX>
struct sax_ignores_number_text : std::false_type {};
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
template<typename BasicJsonType, typename InputAdapterType>
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
@@ -131,7 +131,7 @@ class parser
}
else
{
json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
// in case of an error, return a discarded value
if (!parse_dom(sdp, strict))
@@ -165,10 +165,23 @@ class parser
if (result)
{
return finish_input(*sax, strict);
if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
}
return false;
return result;
}
private:
@@ -189,39 +202,6 @@ class parser
return m_lexer.get_string();
}
/*!
@brief apply the end-of-input handling after a value was parsed
In strict mode, the next token must be the end of the input; otherwise
parse_error.101 is reported through @a sax. In non-strict mode, the
lookahead is released so the caller can keep reading the input right after
the parsed value.
@param[in,out] sax the SAX parser to report an error to
@param[in] strict whether to expect the last token to be EOF
@return true, or what @a sax returned for the error
*/
template<typename SAX>
bool finish_input(SAX& sax, const bool strict)
{
if (strict)
{
// strict mode: next byte must be EOF
if (get_token() != token_type::end_of_input)
{
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return true;
}
/*!
@brief run a DOM SAX parser to completion and position the lexer
@@ -241,7 +221,20 @@ class parser
{
sax_parse_internal(&sdp);
static_cast<void>(finish_input(sdp, strict));
if (strict)
{
// in strict mode, input must be completely read
if (get_token() != token_type::end_of_input)
{
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
}
}
else
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
return !sdp.is_errored();
}
@@ -250,19 +243,12 @@ class parser
JSON_HEDLEY_NON_NULL(2)
bool sax_parse_internal(SAX* sax)
{
// value to avoid a goto (see comment where set to true)
bool skip_to_state_evaluation = false;
// stack to remember the hierarchy of structured values we are parsing
// true = array; false = object (bytes, not std::vector<bool>, whose
// push_back() is a call with GCC)
std::vector<std::uint8_t> states;
// a document that starts with a container pushes onto the stack at
// once: allocate it in one step instead of growing it from 1 byte
if (last_token == token_type::begin_object || last_token == token_type::begin_array)
{
states.reserve(32);
}
// value to avoid a goto (see comment where set to true)
bool skip_to_state_evaluation = false;
while (true)
{
@@ -418,7 +404,7 @@ class parser
{
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
{
return syntax_error(*sax, empty_input_message());
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
}
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
@@ -96,7 +96,7 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
// \u007f under ensure_ascii).
constexpr bool is_ascii_copyable(unsigned char c) noexcept
inline bool is_ascii_copyable(unsigned char c) noexcept
{
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
}
+113 -62
View File
@@ -108,18 +108,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
if (m_object->is_object())
switch (m_object->m_data.m_type)
{
m_it.object_iterator = typename object_t::iterator();
}
else if (m_object->is_array())
{
m_it.array_iterator = typename array_t::iterator();
}
else
{
// primitive (is_primitive(), including discarded)
m_it.primitive_iterator = primitive_iterator_t();
case value_t::object:
{
m_it.object_iterator = typename object_t::iterator();
break;
}
case value_t::array:
{
m_it.array_iterator = typename array_t::iterator();
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator = primitive_iterator_t();
break;
}
}
}
@@ -234,28 +249,33 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
if (m_object->is_object())
switch (m_object->m_data.m_type)
{
m_it.object_iterator = m_object->m_data.m_value.object->end();
}
else if (m_object->is_array())
{
m_it.array_iterator = m_object->m_data.m_value.array->end();
}
else
{
// primitive (is_primitive(), including discarded)
m_it.primitive_iterator.set_end();
}
}
case value_t::object:
{
m_it.object_iterator = m_object->m_data.m_value.object->end();
break;
}
/// @throw invalid_iterator.212 if @a other points to another container
template<typename IterImpl>
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const
{
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
case value_t::array:
{
m_it.array_iterator = m_object->m_data.m_value.array->end();
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator.set_end();
break;
}
}
}
@@ -404,7 +424,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator==(const IterImpl& other) const
{
// if objects are not the same, the comparison is undefined
check_same_container(other);
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr)
@@ -443,7 +466,10 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
bool operator<(const iter_impl& other) const
{
// if objects are not the same, the comparison is undefined
check_same_container(other);
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr)
@@ -452,18 +478,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
return false;
}
if (m_object->is_object())
switch (m_object->m_data.m_type)
{
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
}
case value_t::object:
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
if (m_object->is_array())
{
return (m_it.array_iterator < other.m_it.array_iterator);
}
case value_t::array:
return (m_it.array_iterator < other.m_it.array_iterator);
// primitive (is_primitive(), including discarded)
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
}
}
/*!
@@ -501,19 +534,30 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
if (m_object->is_object())
switch (m_object->m_data.m_type)
{
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
}
case value_t::object:
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array())
{
std::advance(m_it.array_iterator, i);
}
else
{
// primitive (is_primitive(), including discarded)
m_it.primitive_iterator += i;
case value_t::array:
{
std::advance(m_it.array_iterator, i);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator += i;
break;
}
}
return *this;
@@ -569,18 +613,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
{
JSON_ASSERT(m_object != nullptr);
if (m_object->is_object())
switch (m_object->m_data.m_type)
{
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
}
case value_t::object:
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
if (m_object->is_array())
{
return m_it.array_iterator - other.m_it.array_iterator;
}
case value_t::array:
return m_it.array_iterator - other.m_it.array_iterator;
// primitive (is_primitive(), including discarded)
return m_it.primitive_iterator - other.m_it.primitive_iterator;
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return m_it.primitive_iterator - other.m_it.primitive_iterator;
}
}
/*!
@@ -109,25 +109,35 @@ template<typename IteratorType> class iteration_proxy_value
{
JSON_ASSERT(anchor.m_object != nullptr);
if (anchor.m_object->is_array())
switch (anchor.m_object->type())
{
// use integer array index as key
if (array_index != array_index_last)
case value_t::array:
{
int_to_string( array_index_str, array_index );
array_index_last = array_index;
if (array_index != array_index_last)
{
int_to_string( array_index_str, array_index );
array_index_last = array_index;
}
return array_index_str;
}
return array_index_str;
}
if (anchor.m_object->is_object())
{
// use key from the object
return anchor.key();
}
case value_t::object:
return anchor.key();
// use an empty key for all primitive types
return empty_str;
// use an empty key for all primitive types
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return empty_str;
}
}
/// return value of the iterator
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -55,66 +55,6 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
/// output_vector_sink also appends writes of at least this size with insert()
constexpr std::size_t max_reserve_size = 256;
/// @brief copy @a length characters, at most 32, without a call of memcpy()
///
/// Short strings are the typical payload of a binary value, and a call of
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
/// so on down to a single character; each is a fixed-size load and store.
/// Nothing outside of [src, src + length) is read or written.
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
{
JSON_ASSERT(length <= 32);
if (length >= 16)
{
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 16);
std::memcpy(last.data(), src + (length - 16), 16);
std::memcpy(dst, first.data(), 16);
std::memcpy(dst + (length - 16), last.data(), 16);
}
else if (length >= 8)
{
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 8);
std::memcpy(last.data(), src + (length - 8), 8);
std::memcpy(dst, first.data(), 8);
std::memcpy(dst + (length - 8), last.data(), 8);
}
else if (length >= 4)
{
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
std::memcpy(first.data(), src, 4);
std::memcpy(last.data(), src + (length - 4), 4);
std::memcpy(dst, first.data(), 4);
std::memcpy(dst + (length - 4), last.data(), 4);
}
else if (length != 0)
{
// 1 to 3: the first, the middle, and the last character
const CharType a = src[0];
const CharType b = src[length >> 1];
const CharType c = src[length - 1];
dst[0] = a;
dst[length >> 1] = b;
dst[length - 1] = c;
}
}
/// @brief copy @a length characters, of a character type wider than a byte
template<typename CharType>
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
{
std::memcpy(dst, src, length * sizeof(CharType));
}
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -138,7 +78,7 @@ class output_vector_sink
{
public:
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
: v(&vec), position(vec.size())
{}
output_vector_sink(const output_vector_sink&) = delete;
@@ -146,7 +86,7 @@ class output_vector_sink
output_vector_sink& operator=(output_vector_sink&&) = delete;
output_vector_sink(output_vector_sink&& other) noexcept
: v(other.v), cursor(other.cursor), limit(other.limit)
: v(other.v), position(other.position)
{
other.v = nullptr;
}
@@ -155,7 +95,7 @@ class output_vector_sink
{
if (v != nullptr)
{
v->resize(position());
v->resize(position);
}
}
@@ -164,11 +104,11 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c)
{
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
{
grow(1);
}
*cursor++ = c;
(*v)[position++] = c;
}
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
@@ -177,7 +117,7 @@ class output_vector_sink
JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length)
{
if (JSON_HEDLEY_UNLIKELY(space() < length))
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{
if (length >= max_reserve_size)
{
@@ -186,15 +126,10 @@ class output_vector_sink
}
grow(length);
}
if (length <= 32)
if (length != 0)
{
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
cursor += length;
}
else
{
std::memcpy(cursor, s, length * sizeof(CharType));
cursor += length;
std::memcpy(v->data() + position, s, length * sizeof(CharType));
position += length;
}
}
@@ -209,37 +144,21 @@ class output_vector_sink
CharType* reserve(const std::size_t length)
{
JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(space() < length))
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{
grow(length);
}
return cursor;
return v->data() + position;
}
/// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE
void commit(const std::size_t length) noexcept
{
cursor += length;
position += length;
}
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
void flush() noexcept
{}
private:
/// number of characters of *v written so far
std::size_t position() const noexcept
{
return static_cast<std::size_t>(cursor - v->data());
}
/// number of characters that fit the vector behind the write position
std::size_t space() const noexcept
{
return static_cast<std::size_t>(limit - cursor);
}
/// make room for at least @a length more characters after the position;
/// kept out of line so that the write functions stay small enough for
/// GCC to inline them into the writer
@@ -247,15 +166,12 @@ class output_vector_sink
void grow(const std::size_t length)
{
// use the capacity the vector already has, else at least double it;
// never less than min_vector_sink_capacity, so that a small value does not
// never less than 64 characters, so that a small value does not
// reallocate for every few bytes
const std::size_t written = position();
const std::size_t needed = written + length;
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
const std::size_t needed = position + length;
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
cursor = v->data() + written;
limit = v->data() + v->size();
}
/// append @a length (at least max_reserve_size) characters that do not fit
@@ -265,24 +181,20 @@ class output_vector_sink
JSON_HEDLEY_NEVER_INLINE
void append(const CharType* s, const std::size_t length)
{
const std::size_t written = position();
v->resize(written);
if (v->capacity() - written < length)
v->resize(position);
if (v->capacity() - position < length)
{
// grow geometrically, with room for the writes that follow, so
// that the next grow() need not reallocate right away
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
}
v->insert(v->end(), s, s + length);
cursor = v->data() + v->size();
limit = cursor;
position = v->size();
}
std::vector<CharType, AllocatorType>* v;
/// the write position: in *v, at the number of characters written so far
CharType* cursor;
/// the end of *v
CharType* limit;
/// number of characters of *v written so far
std::size_t position;
};
/// output adapter for byte vectors
@@ -365,17 +277,9 @@ class output_string_adapter : public output_adapter_protocol<CharType>
///
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
/// also target arbitrary adapters (output streams, strings, user-provided
/// adapters) via the `output_adapter`-based overloads.
///
/// A write through the adapter is a virtual call, and for a stream a call with
/// a sentry on top, which costs more than encoding the few bytes of most values.
/// The sink therefore collects what is written in a buffer and passes it on in
/// one piece when it is full, a write is too large for it, or @ref flush is
/// called. Everything written reaches the adapter in the same order and with
/// the same bytes; the destructor passes on what is left, so that a write that
/// throws leaves the adapter with exactly the bytes written before it. Only
/// @ref flush can report an error of the adapter (the destructor must not
/// throw), so it has to be called once the value is written.
/// adapters) via the `output_adapter`-based overloads. Each write still goes
/// through one virtual call, exactly as before; only the concrete sinks above
/// avoid it.
template<typename CharType>
class output_adapter_sink
{
@@ -386,121 +290,36 @@ class output_adapter_sink
JSON_ASSERT(oa);
}
output_adapter_sink(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
output_adapter_sink(output_adapter_sink&& other) noexcept
: oa(std::move(other.oa)), used(other.used)
{
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
other.used = 0;
}
~output_adapter_sink()
{
// a normal return has flushed already, so this is for a write that threw
if (used != 0)
{
JSON_TRY
{
flush();
}
JSON_CATCH(...)
{
// nothing can be done about an adapter that throws here
}
}
}
JSON_HEDLEY_ALWAYS_INLINE
void write_character(CharType c)
{
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
{
flush();
}
buffer[used++] = c;
oa->write_character(c);
}
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
// the type-erased path already did before this sink existed
JSON_HEDLEY_ALWAYS_INLINE
void write_characters(const CharType* s, std::size_t length)
{
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
{
write_large(s, length);
return;
}
if (length <= 32)
{
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
used += length;
}
else
{
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
used += length;
}
oa->write_characters(s, length);
}
/// space for writing up to @a length characters (see
/// output_vector_sink::reserve)
JSON_HEDLEY_ALWAYS_INLINE
CharType* reserve(const std::size_t length)
CharType* reserve(const std::size_t length) noexcept
{
JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
{
flush();
}
return buffer.data() + used;
static_cast<void>(length);
return reserved.data();
}
/// keep the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE
void commit(const std::size_t length) noexcept
/// write the first @a length characters written to the space from reserve()
void commit(const std::size_t length)
{
used += length;
}
/// pass everything written so far on to the adapter
void flush()
{
if (used != 0)
{
// reset first: the characters count as written even if the adapter throws
const std::size_t length = used;
used = 0;
oa->write_characters(buffer.data(), length);
}
oa->write_characters(reserved.data(), length);
}
private:
/// a write that does not fit the space left in the buffer
JSON_HEDLEY_NEVER_INLINE
void write_large(const CharType* s, const std::size_t length)
{
flush();
if (length >= buffer_size)
{
oa->write_characters(s, length);
}
else
{
std::memcpy(buffer.data(), s, length * sizeof(CharType));
used = length;
}
}
/// large enough for reserve() at any time
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
output_adapter_t<CharType> oa;
/// number of characters of @ref buffer not yet passed on
std::size_t used = 0;
std::array<CharType, buffer_size> buffer{};
std::array<CharType, max_reserve_size> reserved{};
};
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
File diff suppressed because it is too large. Load diff
+4 -87
View File
@@ -48,24 +48,6 @@ inline std::string hex_byte(const std::uint8_t byte)
return result;
}
/*!
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
serializers, whose output is lowercase. Writes exactly four characters to
@a out and returns the pointer past them.
*/
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
{
constexpr const char* nibble_to_hex = "0123456789abcdef";
const unsigned int unit = codeunit;
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
out[3] = nibble_to_hex[unit & 0x0Fu];
return out + 4;
}
///////////////////
// UTF-8 encoding //
///////////////////
@@ -121,66 +103,6 @@ void encode_utf8(std::uint32_t cp, const Out& out)
}
}
/*!
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
to 0xFF). For a multibyte sequence, the second byte must lie in
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
beyond U+10FFFF (0xF4).
*/
struct utf8_sequence_shape
{
std::uint8_t length;
std::uint8_t second_lo;
std::uint8_t second_hi;
};
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
{
if (lead < 0x80u)
{
return {1, 0, 0};
}
if (lead < 0xC2u)
{
return {0, 0, 0};
}
if (lead <= 0xDFu)
{
return {2, 0x80u, 0xBFu};
}
if (lead == 0xE0u)
{
return {3, 0xA0u, 0xBFu};
}
if (lead == 0xEDu)
{
return {3, 0x80u, 0x9Fu};
}
if (lead <= 0xEFu)
{
return {3, 0x80u, 0xBFu};
}
if (lead == 0xF0u)
{
return {4, 0x90u, 0xBFu};
}
if (lead <= 0xF3u)
{
return {4, 0x80u, 0xBFu};
}
if (lead == 0xF4u)
{
return {4, 0x80u, 0x8Fu};
}
return {0, 0, 0};
}
///////////////////
// UTF-8 decoding //
///////////////////
@@ -196,7 +118,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
written by Björn Hoehrmann. See
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five
The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
places, which differ in speed, diagnostics, and how they read the input:
- decode() below: the serializer, to escape and, in strict mode, reject
@@ -210,15 +132,10 @@ places, which differ in speed, diagnostics, and how they read the input:
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
They must accept exactly what the lexer's switch accepts.
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
access, and the bytes the bulk path leaves to it. It keeps its own ranges:
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
json_document, with the index of the offending byte. It also uses
utf8_shape().
access, and the bytes the bulk path leaves to it.
All five must accept the same set of sequences, which the test "UTF-8
validators accept the same sequences" in unit-class_lexer.cpp checks
exhaustively against decode() and utf8_shape().
All four must accept the same set of sequences, so a change to one needs a
matching change to the others.
@param[in,out] state the current decoder state
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
-30
View File
@@ -177,36 +177,6 @@ inline bool owns_memory(const value_t t) noexcept
}
}
/*!
@brief whether a value of the given type is neither an object nor an array
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
the types that iterators walk with a primitive_iterator_t (a single value, or no
value for null and discarded) and that JSON Pointer resolution and the
serializer treat as leaves. Every enumerator is listed so that -Wswitch-enum
flags this function when a new type is added.
*/
inline bool is_primitive(const value_t t) noexcept
{
switch (t)
{
case value_t::object:
case value_t::array:
return false;
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return true;
}
}
/*!
@brief compare an integer with a floating point number without precision loss
+47 -163
View File
@@ -275,34 +275,11 @@ class builder
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
};
// The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows,
// and by `write_back` on every return.
const unsigned char* const end = m_end;
const std::size_t start = arena_used();
char* w = m_arena_write;
const char* w_end = m_arena_end;
const arena_write_back write_back(*this, w);
{
const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16)
{
arena_ensure(w, w_end, 16);
std::memcpy(w, s, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(w, w_end, n);
std::memcpy(w, s, n);
}
w += n;
}
arena_run(s, static_cast<std::size_t>(p - s));
for (;;)
{
if (p == end)
if (p == m_end)
{
return failed(error_code::string_missing_quote, p);
}
@@ -310,9 +287,7 @@ class builder
if (c == '"')
{
++p;
// (from w: `write_back` stores it only after the return value
// is made; the arena is allocated, since an escape was written)
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
return decoded{p, start, arena_used() - start};
}
if (c != '\\')
{
@@ -322,7 +297,7 @@ class builder
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
}
++p;
if (p == end)
if (p == m_end)
{
return failed(error_code::string_missing_quote, p);
}
@@ -340,7 +315,7 @@ class builder
{
return failed(error_code::string_surrogate_low, p);
}
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
{
return failed(error_code::string_surrogate_high, p);
}
@@ -356,106 +331,26 @@ class builder
}
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
}
arena_ensure(w, w_end, 4);
w = put_utf8(w, cp);
arena_ensure(4);
m_arena_write = put_utf8(m_arena_write, cp);
}
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{
arena_ensure(w, w_end, 1);
*w++ = simple_escape[d];
arena_ensure(1);
*m_arena_write++ = simple_escape[d];
}
else
{
--p;
return failed(error_code::string_escape, p);
}
#if NLOHMANN_VIEW_NEON
if (p != end && *p == '\\')
if (p != m_end && *p == '\\')
{
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
}
// The first 16 bytes of the run are checked one by one, in predicted branches, and copied
// as one block: where the runs between escapes are short, the vector step below would put
// its longer dependency chain (load, compare, mask, count, add) on p for each of them,
// and be slower than these steps (the scanner of the keys works the same way)
{
if (end - p >= 16)
{
const std::uint8_t* const plain = string_plain();
std::size_t steps = 16;
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { steps = (i); goto run_found; }
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP
run_found:
arena_ensure(w, w_end, 16);
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps;
p += steps;
if (steps < 16)
{
if (*p >= 0x80)
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
arena_ensure(w, w_end, n);
std::memcpy(w, r, n);
w += n;
}
continue;
}
}
}
#else
if (p != end && (*p == '\\' || *p == '"'))
{
continue; // consecutive escapes ("\u00e4\u00f6"), or the end of the string: no run in between
}
#endif
#if NLOHMANN_VIEW_VECTOR
// the plain bytes up to the next quote, backslash, or control
// character are copied as they are scanned, 16 bytes at a time
for (;;)
{
if (NLOHMANN_VIEW_UNLIKELY(end - p < 16))
{
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
arena_ensure(w, w_end, n);
std::memcpy(w, r, n);
w += n;
break;
}
arena_ensure(w, w_end, 16);
const unsigned k = copy_plain_run16(p, w);
w += k;
p += k;
if (k == 16)
{
continue;
}
if (NLOHMANN_VIEW_UNLIKELY(*p >= 0x80))
{
// non-ASCII: the UTF-8 check, and the rest of the run
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
arena_ensure(w, w_end, n);
std::memcpy(w, r, n);
w += n;
}
break;
}
#else
const unsigned char* const r = p;
p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r);
arena_ensure(w, w_end, n);
std::memcpy(w, r, n);
w += n;
#endif
p = scan_string_run(p, m_end);
arena_run(r, static_cast<std::size_t>(p - r));
}
}
@@ -497,49 +392,19 @@ run_found:
char* m_arena_write = nullptr;
char* m_arena_end = nullptr;
/// room for n more bytes at w, the write position kept in a local, with
/// w_end the end of the arena. (A lambda instead is not always inlined,
/// and then w would be kept in memory, as m_arena_write is.)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < n))
{
m_arena_write = w;
arena_grow(n);
w = m_arena_write;
w_end = m_arena_end;
}
}
/// stores a write position kept in a local back into m_arena_write when
/// the function that keeps it returns, on every path
class arena_write_back
{
public:
arena_write_back(builder& b, char*& w) noexcept
: m_builder(b)
, m_w(w)
{}
~arena_write_back()
{
m_builder.m_arena_write = m_w;
}
arena_write_back(const arena_write_back&) = delete;
arena_write_back& operator=(const arena_write_back&) = delete;
arena_write_back(arena_write_back&&) = delete;
arena_write_back& operator=(arena_write_back&&) = delete;
private:
builder& m_builder;
char*& m_w;
};
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
{
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
}
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(m_arena_end - m_arena_write) < n))
{
arena_grow(n);
}
}
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
{
const std::size_t used = arena_used();
@@ -548,6 +413,25 @@ run_found:
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
}
/// append the run [r, r + n) to the arena; short runs as one fixed-size
/// 16-byte move when both sides have the room (no library call)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_run(const unsigned char* r, std::size_t n)
{
if (n <= 16 && m_end - r >= 16)
{
arena_ensure(16);
std::memcpy(m_arena_write, r, 16);
}
else if (n != 0)
{
// (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes)
arena_ensure(n);
std::memcpy(m_arena_write, r, n);
}
m_arena_write += n;
}
/// UTF-8 encoding of cp at w (room for 4 bytes); returns the position after
/// it. (A returned pointer stays in a register; writing through a
/// reference to m_arena_write would store and reload it around every byte.)
@@ -1070,11 +954,11 @@ indent_done:
++p;
}
const unsigned char* const int_start = p;
if (cur() == '0')
if (p != e && *p == '0')
{
++p;
}
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
{
p = skip_digits(p + 1, e);
}
@@ -1085,7 +969,7 @@ indent_done:
const auto int_digits = static_cast<std::size_t>(p - int_start);
std::size_t frac_digits = 0;
bool is_float = false;
if (cur() == '.')
if (p != e && *p == '.')
{
++p;
const unsigned char* const f0 = p;
@@ -1098,20 +982,20 @@ indent_done:
is_float = true;
}
std::int64_t exponent = 0;
if ((cur() | 0x20) == 'e')
if (p != e && (*p | 0x20) == 'e')
{
++p;
bool exp_negative = false;
if (cur() == '+' || cur() == '-')
if (p != e && (*p == '+' || *p == '-'))
{
exp_negative = *p == '-';
++p;
}
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
{
return fail(error_code::number_after_exponent);
}
while (is_digit(cur()))
while (p != e && is_digit(*p))
{
if (exponent < 100000)
{
@@ -1173,7 +1057,7 @@ indent_done:
++p; // opening quote
const unsigned char* const s = p;
p = scan_string_run<Value>(p, e);
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
{
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
++p;
+41 -19
View File
@@ -51,6 +51,11 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
inline void check_utf8(const char* s, std::size_t n)
{
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const auto hex = [](unsigned char c)
{
constexpr const char* digits = "0123456789ABCDEF";
return std::string{digits[c >> 4u], digits[c & 0xFu]};
};
for (std::size_t i = 0; i < n;)
{
const unsigned char c = p[i];
@@ -59,22 +64,39 @@ inline void check_utf8(const char* s, std::size_t n)
++i;
continue;
}
const utf8_sequence_shape shape = utf8_shape(c);
const std::size_t len = shape.length;
if (len == 0)
std::size_t len = 0;
unsigned char lo = 0x80;
unsigned char hi = 0xBF;
if (c >= 0xC2 && c <= 0xDF)
{
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
len = 2;
}
else if (c >= 0xE0 && c <= 0xEF)
{
len = 3;
lo = c == 0xE0 ? 0xA0 : 0x80;
hi = c == 0xED ? 0x9F : 0xBF;
}
else if (c >= 0xF0 && c <= 0xF4)
{
len = 4;
lo = c == 0xF0 ? 0x90 : 0x80;
hi = c == 0xF4 ? 0x8F : 0xBF;
}
else
{
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
}
for (std::size_t k = 1; k < len; ++k)
{
if (i + k == n)
{
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
}
const unsigned char b = p[i + k];
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
{
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
}
}
i += len;
@@ -122,7 +144,7 @@ class editor
node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
{
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
}
check_utf8(key.data(), key.size());
const encoded e = encode(std::forward<V>(value));
@@ -173,7 +195,7 @@ class editor
node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array))
{
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
}
check_index(idx, a->len);
const encoded e = encode(std::forward<V>(value));
@@ -190,7 +212,7 @@ class editor
node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
{
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
throw_type_error(308, "cannot use push_back() with ", array.type_name());
}
const encoded e = encode(std::forward<V>(value));
if (a->kind == static_cast<std::uint8_t>(value_t::null))
@@ -214,7 +236,7 @@ class editor
node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array))
{
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
throw_type_error(309, "cannot use insert() with ", array.type_name());
}
check_index(idx, a->len + 1);
const encoded e = encode(std::forward<V>(value));
@@ -234,7 +256,7 @@ class editor
node* const o = own(object);
if (o->kind != static_cast<std::uint8_t>(value_t::object))
{
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
throw_type_error(307, "cannot use erase() with ", object.type_name());
}
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
{
@@ -252,7 +274,7 @@ class editor
node* const a = own(array);
if (a->kind != static_cast<std::uint8_t>(value_t::array))
{
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
throw_type_error(307, "cannot use erase() with ", array.type_name());
}
check_index(idx, a->len);
node* const h = block_of(m_doc, a, 0);
@@ -275,7 +297,7 @@ class editor
{
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
{
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
throw_invalid_iterator(202, "view does not belong to this document");
}
edit_state_of(m_doc);
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
@@ -285,7 +307,7 @@ class editor
{
if (idx >= limit)
{
throw_array_index_out_of_range(idx);
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
}
}
@@ -426,7 +448,7 @@ class editor
{
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
{
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
throw_type_error(302, "type must be a value, but is ", "discarded");
}
encoded r;
if (!is_container(*v.m_node))
@@ -576,7 +598,7 @@ class editor
{
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
{
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
}
node n = plain_node(value_t::string);
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
@@ -651,12 +673,12 @@ class editor
return string_node(s.data(), s.size());
}
case value_t::binary:
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
throw_type_error(319, "cannot store a binary value in a json_document", "");
case value_t::discarded:
case value_t::object:
case value_t::array:
default:
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
throw_type_error(302, "type must be a value, but is ", "discarded");
}
}
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
constexpr std::size_t limit = 0xFFFFFFFFu;
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
{
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
}
const std::size_t needed = used + n;
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
document_data::edit_state& e = edit_state_of(d);
if (e.moved.size() >= 0xFFFFFFFFu)
{
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
}
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
{
+7 -27
View File
@@ -25,52 +25,32 @@ namespace view
// Exceptions are thrown out of line, so that the accessors that may throw stay
// small enough to be inlined.
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
{
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
}
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
{
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
}
/// a parse error without a position (as those of json_pointer)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
{
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
}
/// out_of_range.401: the array index @a idx is not below the size
template<typename Index>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
{
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
}
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
{
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
}
/// out_of_range.403: the object has no member @a key
template<typename Key>
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
{
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
}
/// SAX handler of the error replay: accepts every event without storing it,
/// and throws the exception the parser reports
template<typename BasicJsonType>
@@ -169,7 +149,7 @@ template<typename BasicJsonType>
if (f.code == error_code::input_too_large)
{
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
}
throwing_sax<BasicJsonType> sax;
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
@@ -189,7 +169,7 @@ template<typename BasicJsonType>
}
}
pos.chars_read_current_line = off + 1 - line_start;
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
// LCOV_EXCL_STOP
}
+4 -4
View File
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
inline std::vector<std::uint8_t> save_image(const document_data& d)
{
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
const std::size_t arena_size = d.arena_size;
std::vector<node> compacted;
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
{
// LCOV_EXCL_START (4 GiB)
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
// LCOV_EXCL_STOP
}
image_header h{};
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
{
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", 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;
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
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(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
#endif
if (image == nullptr || size < sizeof(image_header))
{
-34
View File
@@ -10,9 +10,6 @@
#include <algorithm> // max
#include <cstddef> // size_t
#ifndef JSON_NO_IO
#include <cstdio> // FILE, fread
#endif
#include <istream> // istream, streambuf
#include <string> // basic_string, char_traits, string
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
@@ -157,37 +154,6 @@ inline void append_stream(std::string& buf, std::istream& is)
is.clear(is.rdstate() | std::ios::eofbit);
}
/// a pointer to a C stream (never with JSON_NO_IO, which leaves out <cstdio>)
template<typename T>
struct is_c_stream
#ifndef JSON_NO_IO
: std::is_same<T, std::FILE*>
#else
: std::false_type
#endif
{};
#ifndef JSON_NO_IO
/// append the rest of a C stream: in blocks with fread, not character by
/// character (the library's file adapter reads with fgetc);
/// f is not null
inline void append_file(std::string& buf, std::FILE* f)
{
for (;;)
{
const std::size_t used = buf.size();
const std::size_t room = (std::max)(buf.capacity() - used, std::size_t{4096});
buf.resize(used + room);
const std::size_t got = std::fread(&buf[used], 1, room, f);
buf.resize(used + got);
if (got < room)
{
break; // end of file, or an error (as fgetc, which returns EOF for both)
}
}
}
#endif
} // namespace view
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END
+9 -11
View File
@@ -110,7 +110,7 @@ class view_iterator
{
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
{
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
}
return string_view_t(m_doc->str(*m_pos), m_pos->len);
}
@@ -147,21 +147,20 @@ class view_item
view_item(const iterator& it, std::size_t index)
: m_it(it)
, m_position(index)
{}
{
if (!it.is_object_iterator())
{
m_index = std::to_string(index);
}
}
/// the member key, or the element index for arrays (as text made on the
/// first call, so that iterating over the values pays nothing for it)
/// the member key, or the element index for arrays
string_view_t key() const
{
if (m_it.is_object_iterator())
{
return m_it.key();
}
if (m_index.empty())
{
m_index = std::to_string(m_position);
}
return string_view_t(m_index.data(), m_index.size());
}
@@ -184,8 +183,7 @@ class view_item
private:
iterator m_it;
std::size_t m_position; ///< the index of an array element
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
std::string m_index{}; // NOLINT(readability-redundant-member-init)
};
/// the range returned by basic_json_view::items()
-8
View File
@@ -166,10 +166,6 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
{
return e + idx; // a growable block: one link per element
}
if (array->next == array->len + 1u && (!nav::editable() || (array->flags & node_flags::moved) == 0))
{
return e + idx; // no element has a subtree: the elements are the nodes after the array
}
for (std::size_t i = 0; i < idx; ++i)
{
e = document_data::after(e);
@@ -185,10 +181,6 @@ const node* last_child(const document_data& d, const node* container) noexcept
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
const node* const end = navigation<Editable>::end(d, container);
const node* last = navigation<Editable>::first(d, container);
if (value_offset == 0 && container->next == container->len + 1u && (!navigation<Editable>::editable() || (container->flags & node_flags::moved) == 0))
{
return last + (container->len - 1u); // an array without subtrees: the elements are the nodes after it
}
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
{
last = c;
+7 -45
View File
@@ -8,7 +8,7 @@
#pragma once
#include <array> // array
#include <algorithm> // max
#include <cstddef> // size_t
#include <cstdint> // int64_t, uint8_t
#include <string> // string
@@ -40,13 +40,12 @@ template<typename BasicJsonType, bool Editable>
BasicJsonType materialize(const document_data& d, const node* n)
{
using string_t = typename BasicJsonType::string_t;
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
using nav = navigation<Editable>;
// the open containers: their next element (or key of the next member),
// their end, and whether they are objects; for the first 16 levels on the
// stack, else sized once from the document's deepest nesting (for a
// subtree an upper bound)
// their end, and whether they are objects; sized once from the
// document's deepest nesting (for a subtree an upper bound)
struct frame
{
const node* pos;
@@ -54,43 +53,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
bool object;
};
// a scalar needs neither the handler nor the stack of open containers
switch (static_cast<value_t>(n->kind))
{
case value_t::string:
return BasicJsonType(string_t(d.str(*n), n->len));
case value_t::number_integer:
return BasicJsonType(static_cast<typename BasicJsonType::number_integer_t>(static_cast<std::int64_t>(integer_bits(*n))));
case value_t::number_unsigned:
return BasicJsonType(static_cast<typename BasicJsonType::number_unsigned_t>(integer_bits(*n)));
case value_t::number_float:
return BasicJsonType(float_value<typename BasicJsonType::number_float_t>(d, *n));
case value_t::boolean:
return BasicJsonType((n->flags & node_flags::is_true) != 0);
case value_t::null:
return BasicJsonType(nullptr);
case value_t::object:
case value_t::array:
case value_t::binary:
case value_t::discarded:
default:
break;
}
BasicJsonType result;
sax_t sax(result, true);
const string_t no_token{};
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
std::vector<frame> deep{};
frame* open = shallow.data();
std::size_t capacity = shallow.size();
if (d.max_depth > capacity)
{
deep.resize(d.max_depth);
open = deep.data();
capacity = deep.size();
}
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
std::size_t depth = 0;
for (;;)
{
@@ -110,13 +76,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
{
sax.start_array(n->len);
}
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
{
// (deeper than the recorded nesting, e.g. after edits)
deep.assign(open, open + depth);
deep.resize(depth * 2);
open = deep.data();
capacity = deep.size();
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
}
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
break;
+11 -101
View File
@@ -47,96 +47,27 @@ constexpr std::size_t index_max_displacement = 64;
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
"a table must be much larger than the probe window of a lookup");
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
/// last word may overlap the one before (so that no loop over single bytes is
/// needed); a key of fewer than eight bytes is read as two overlapping words of
/// four or two bytes (in the native byte order: the hash is not stored, and the
/// same on both sides of a lookup)
/// hash of a key: its bytes, eight at a time, in a fixed byte order
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
{
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
while (n >= 8)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
n -= 8;
}
std::uint64_t w = 0;
if (n >= 8)
for (std::size_t i = 0; i < n; ++i)
{
const unsigned char* const last = p + n - 8;
while (p < last)
{
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
h ^= h >> 29u;
p += 8;
}
w = read_eight_bytes(last);
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
}
else if (n >= 4)
{
std::uint32_t head = 0;
std::uint32_t tail = 0;
std::memcpy(&head, p, 4);
std::memcpy(&tail, p + n - 4, 4);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 32u);
}
else if (n >= 2)
{
std::uint16_t head = 0;
std::uint16_t tail = 0;
std::memcpy(&head, p, 2);
std::memcpy(&tail, p + n - 2, 2);
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
}
else if (n == 1)
{
w = p[0];
}
// (a product only carries bits upwards, and the slot is taken from the low
// bits: the high bits of the last word are folded down before the multiplication)
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
h = (h ^ w) * 0x94D049BB133111EBu;
return h ^ (h >> 31u);
}
/// whether the n <= 16 bytes at a and b are equal, as two overlapping words
/// (the first and the last 8, 4, or 2 bytes) and without a call
NLOHMANN_VIEW_ALWAYS_INLINE bool same_short_bytes(const unsigned char* a, const unsigned char* b, std::size_t n) noexcept
{
if (n >= 8)
{
std::uint64_t x0 = 0;
std::uint64_t x1 = 0;
std::uint64_t y0 = 0;
std::uint64_t y1 = 0;
std::memcpy(&x0, a, 8);
std::memcpy(&x1, a + n - 8, 8);
std::memcpy(&y0, b, 8);
std::memcpy(&y1, b + n - 8, 8);
return x0 == y0 && x1 == y1;
}
if (n >= 4)
{
std::uint32_t x0 = 0;
std::uint32_t x1 = 0;
std::uint32_t y0 = 0;
std::uint32_t y1 = 0;
std::memcpy(&x0, a, 4);
std::memcpy(&x1, a + n - 4, 4);
std::memcpy(&y0, b, 4);
std::memcpy(&y1, b + n - 4, 4);
return x0 == y0 && x1 == y1;
}
if (n >= 2)
{
std::uint16_t x0 = 0;
std::uint16_t x1 = 0;
std::uint16_t y0 = 0;
std::uint16_t y1 = 0;
std::memcpy(&x0, a, 2);
std::memcpy(&x1, a + n - 2, 2);
std::memcpy(&y0, b, 2);
std::memcpy(&y1, b + n - 2, 2);
return x0 == y0 && x1 == y1;
}
return n == 0 || *a == *b;
}
/// build the table of a large object
inline void build_object_index(document_data& d, node* obj)
{
@@ -201,27 +132,6 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
const std::uint32_t* const table = d.index_slots.data() + ix.start;
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
const auto* const k_bytes = reinterpret_cast<const unsigned char*>(key); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
// (the loop for longer keys comes first, as the code is laid out; it is not the more common case)
if (NLOHMANN_VIEW_UNLIKELY(n <= 16))
{
// (up to 16 bytes, the bytes are compared as two words, without a call)
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
if (s == 0)
{
return nullptr;
}
const node* const k = obj + s;
if (k->len == n && same_short_bytes(reinterpret_cast<const unsigned char*>(d.str(*k)), k_bytes, n)) // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
{
return k;
}
slot = (slot + 1) & ix.mask;
}
return nullptr;
}
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
{
const std::uint32_t s = table[slot];
+50 -14
View File
@@ -32,15 +32,51 @@ enum class pointer_mode
contains, ///< as basic_json::contains(): no exceptions at all
};
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
using index_status = detail::array_index_status;
/// the outcome of reading an array index from a reference token
enum class index_status
{
ok,
leading_zero, ///< parse_error.106
not_number, ///< parse_error.109
unresolved, ///< out_of_range.404
too_large, ///< out_of_range.410
};
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
/// but reports errors instead of throwing them
template<typename StringType>
index_status array_index(const StringType& s, std::size_t& idx) noexcept
{
return detail::parse_array_index(s, idx);
// as basic_json: the empty token and "-" (past the last element) are
// unresolved rather than not a number
if (s.empty() || (s.size() == 1 && s[0] == '-'))
{
return index_status::unresolved;
}
if (s[0] < '0' || s[0] > '9')
{
return index_status::not_number;
}
if (s.size() > 1 && s[0] == '0')
{
return index_status::leading_zero;
}
std::uint64_t v = 0;
for (const auto c : s)
{
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
{
return index_status::unresolved; // not a number, or beyond unsigned long long
}
v = (v * 10) + d;
}
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
{
return index_status::too_large;
}
idx = static_cast<std::size_t>(v);
return index_status::ok;
}
/// throws the exception json_pointer::array_index throws for this status
@@ -50,15 +86,15 @@ template<typename StringType>
switch (status)
{
case index_status::leading_zero:
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_a_number:
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
case index_status::exceeds_size_type:
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
case index_status::not_number:
throw_parse_error(109, concat("array index '", s, "' is not a number"));
case index_status::too_large:
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
case index_status::unresolved:
case index_status::ok:
default:
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
}
}
@@ -83,7 +119,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (mode == pointer_mode::checked)
{
throw_key_not_found(token);
throw_out_of_range(403, concat("key '", token, "' not found"));
}
return View();
}
@@ -95,7 +131,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (throwing)
{
throw_array_index_past_end(cur.size());
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
}
return View();
}
@@ -103,7 +139,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
const index_status status = array_index(token, idx);
if (status != index_status::ok)
{
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
if (throwing || (mode == pointer_mode::value && parse_error))
{
throw_array_index_error(status, token);
@@ -114,7 +150,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (mode == pointer_mode::checked)
{
throw_array_index_out_of_range(idx);
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
}
return View();
}
@@ -124,7 +160,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
{
if (throwing)
{
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
}
return View();
}
+265 -378
View File
@@ -23,7 +23,6 @@
#include <nlohmann/detail/view/macro_scope.hpp>
#include <nlohmann/detail/view/node.hpp>
#include <nlohmann/detail/view/number.hpp>
#include <nlohmann/detail/view/simd.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
@@ -81,6 +80,13 @@ class output_buffer
put(s, N - 1);
}
void put_repeated(char c, std::size_t n)
{
reserve(n);
std::memset(m_pos, c, n);
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
@@ -137,27 +143,9 @@ class output_buffer
/// 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
{
std::size_t i = 0;
#if NLOHMANN_VIEW_SSE2
// (16 bytes per step; with NEON, the mask of a step costs more than it saves on the
// short runs that are typical here)
// NOLINTBEGIN(portability-simd-intrinsics)
for (; i + 16 <= n; i += 16)
{
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(s + i))); // NOLINT(bugprone-casting-through-void)
// (a byte is below 0x20 if it is its own minimum with 0x1F)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmpeq_epi8(_mm_min_epu8(in, _mm_set1_epi8(0x1F)), in));
const auto mask = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
if (mask != 0)
{
return i + static_cast<std::size_t>(count_trailing_zeros(mask));
}
}
// NOLINTEND(portability-simd-intrinsics)
#endif
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);
@@ -208,70 +196,6 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
return true;
}
#if NLOHMANN_VIEW_VECTOR
/// the bytes of the 16 at p that ensure_ascii cannot copy (a quote, a
/// backslash, a control character, DEL, or not ASCII; a signed compare with
/// 0x20 finds control characters and non-ASCII bytes at once), one bit per
/// byte (x86) or one nibble per byte (NEON)
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t ascii_stop_mask(const unsigned char* p) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vorrq_u8(vceqq_u8(in, vdupq_n_u8(0x7F)), vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20))));
return vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F)), _mm_cmplt_epi8(in, _mm_set1_epi8(0x20))));
return static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
#endif
}
/// the width in bits of a byte in the mask of ascii_stop_mask()
constexpr unsigned ascii_stop_bits() noexcept
{
return NLOHMANN_VIEW_NEON ? 4u : 1u;
}
#endif
/// As find_ascii_copyable_run(), 16 bytes per step. end is the end of the
/// buffer that holds the n bytes at p (a short run is read as 16 bytes if
/// they are in the buffer).
inline std::size_t ascii_copyable_run(const unsigned char* p, std::size_t n, const unsigned char* end) noexcept
{
#if NLOHMANN_VIEW_VECTOR
if (n >= 16)
{
std::size_t i = 0;
for (; i + 16 <= n; i += 16)
{
const std::uint64_t mask = ascii_stop_mask(p + i);
if (mask != 0)
{
return i + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits());
}
}
if (i == n)
{
return n;
}
// the last 16 bytes: those before i are known to be copyable
const std::uint64_t mask = ascii_stop_mask(p + n - 16);
return mask != 0 ? n - 16 + (static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits()) : n;
}
if (end - p >= 16)
{
// (the bytes after the run are masked out)
const std::uint64_t mask = ascii_stop_mask(p) & ((std::uint64_t{1} << (n * ascii_stop_bits())) - 1u);
return mask != 0 ? static_cast<std::size_t>(count_trailing_zeros(mask)) / ascii_stop_bits() : n;
}
#else
static_cast<void>(end);
#endif
return find_ascii_copyable_run(p, 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
@@ -351,39 +275,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
return w + N - 1;
}
/// the depth of the innermost open container, counted only for indented output
template<bool Counted>
struct depth_counter
{
std::size_t value = 0;
void enter() noexcept
{
++value;
}
std::size_t leave() noexcept
{
return --value;
}
std::size_t get() const noexcept
{
return value;
}
};
template<>
struct depth_counter<false>
{
void enter() noexcept {}
std::size_t leave() noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
std::size_t get() const noexcept // NOLINT(readability-convert-member-functions-to-static): same interface as depth_counter<true>
{
return 0;
}
};
/// how the view's dump() writes a value
struct dump_style
{
@@ -420,95 +311,139 @@ class view_serializer
void dump(const node* root)
{
if (m_style.ensure_ascii)
if (!m_style.pretty)
{
dump_ascii(root);
if (m_style.ensure_ascii)
{
dump_compact_ascii(root);
}
else if (m_style.source_numbers)
{
dump_compact<true, false>(root);
}
else
{
dump_compact<false, false>(root);
}
return;
}
else if (m_style.pretty)
struct frame
{
dump_pretty(root);
}
else if (m_style.source_numbers)
const node* pos; ///< next element, or key of the next member
const node* end;
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);
const node* n = root;
for (;;)
{
dump_text<true, false, false>(root);
}
else
{
dump_text<false, false, false>(root);
// write the value at n
if (is_container(*n))
{
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
if (n->len == 0)
{
m_out.put(object ? "{}" : "[]", 2);
}
else
{
m_out.put(object ? '{' : '[');
stack.push_back(frame{nav::first(m_doc, n), nav::end(m_doc, n), object, true});
}
}
else
{
write_scalar(*n);
}
// go to the next value: close finished containers, then separate
for (;;)
{
if (stack.empty())
{
m_out.finish();
return;
}
if (stack.back().pos == stack.back().end)
{
const bool object = stack.back().object;
stack.pop_back();
newline(stack.size());
m_out.put(object ? '}' : ']');
continue;
}
frame& f = stack.back();
if (!f.first)
{
m_out.put(',');
}
f.first = false;
newline(stack.size());
if (f.object)
{
write_string(*f.pos);
if (m_style.pretty)
{
m_out.put(": ");
}
else
{
m_out.put(':');
}
n = nav::value(f.pos + 1);
f.pos = document_data::after(f.pos + 1);
}
else
{
n = nav::value(f.pos);
f.pos = document_data::after(f.pos);
}
break;
}
}
}
private:
/// dump_text() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_ascii(const node* root)
{
if (m_style.pretty)
{
if (m_style.source_numbers)
{
dump_text<true, true, true>(root);
}
else
{
dump_text<false, true, true>(root);
}
}
else if (m_style.source_numbers)
{
dump_text<true, false, true>(root);
}
else
{
dump_text<false, false, true>(root);
}
}
/// dump_text() with indentation, out of line: the compact walk is faster
/// when it is not merged with the indented ones into dump()
NLOHMANN_VIEW_NOINLINE void dump_pretty(const node* root)
{
if (m_style.source_numbers)
{
dump_text<true, true, false>(root);
}
else
{
dump_text<false, true, false>(root);
}
}
/// 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. If validate is set, the string must be valid UTF-8: the
/// runs are found by the lexer's scan, which validates the sequences, and
/// invalid UTF-8 throws what basic_json::dump() throws. (The compact output
/// writes the bytes of a damaged image unchanged, and scans without
/// validating them.)
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n, bool validate)
/// 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 = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
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;
}
if (validate && s[i] >= 0x80)
{
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
}
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
++i;
}
m_out.put('"');
}
/// whether dump_text() copies a string or key from the source: one of
/// dump_compact() with ensure_ascii, out of line: the walk without it is
/// faster when it is not merged with this one into dump()
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
{
if (m_style.source_numbers)
{
dump_compact<true, true>(root);
}
else
{
dump_compact<false, true>(root);
}
}
/// whether dump_compact() copies a string or key from the source: one of
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
/// each, so that the walk without ensure_ascii is compiled exactly as
/// before: with GCC, a condition that only folds away later changed its
@@ -523,155 +458,13 @@ class view_serializer
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
}
/*!
@brief a string with ensure_ascii: the characters that are not printable ASCII as \\uXXXX
(a character beyond U+FFFF as a surrogate pair); 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
*/
NLOHMANN_VIEW_NOINLINE void write_ascii(const node& n)
/// a string with ensure_ascii (dump_compact()), out of line
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
{
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const std::size_t size = n.len;
char* w = m_out.cursor();
char* lim = m_out.limit();
// room for k bytes
const auto room = [&](std::size_t k)
{
const auto left = static_cast<std::size_t>(lim - w);
if (NLOHMANN_VIEW_UNLIKELY(left < k))
{
m_out.set_cursor(w);
m_out.reserve(k);
w = m_out.cursor();
lim = m_out.limit();
}
};
static constexpr const char* hex = "0123456789abcdef";
// \\uXXXX
const auto u_escape = [&](std::uint32_t u)
{
w[0] = '\\';
w[1] = 'u';
w[2] = hex[(u >> 12u) & 0xFu];
w[3] = hex[(u >> 8u) & 0xFu];
w[4] = hex[(u >> 4u) & 0xFu];
w[5] = hex[u & 0xFu];
w += 6;
};
room(1);
*w++ = '"';
std::size_t i = 0;
// (a string of the source starts with a run that has nothing to escape, 16 bytes per step)
const std::size_t prefix = (n.flags & node_flags::storage) == 0 ? ascii_copyable_run(s, size, reinterpret_cast<const unsigned char*>(m_doc.src + m_doc.size)) : 0; // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
if (prefix != 0)
{
room(prefix);
std::memcpy(w, s, prefix);
w += prefix;
i = prefix;
}
while (i < size)
{
// (a character that needs an escape sequence is not worth a call)
const std::size_t run = is_ascii_copyable(s[i]) ? find_ascii_copyable_run(s + i, size - i) : 0;
if (run != 0)
{
room(run);
std::memcpy(w, s + i, run);
w += run;
i += run;
continue;
}
room(12);
if (s[i] >= 0x80)
{
const unsigned char lead = s[i];
// (the two-byte sequences of Latin text and the three-byte sequences of most other
// text, with continuation bytes of any value: the ranges that are restricted (E0,
// ED, and the sequences of four bytes) go through validate_one_utf8())
if (lead >= 0xE1 && lead <= 0xEF && lead != 0xED && size - i >= 3 && (s[i + 1] & 0xC0u) == 0x80 && (s[i + 2] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x0Fu) << 12u) | ((s[i + 1] & 0x3Fu) << 6u) | (s[i + 2] & 0x3Fu));
i += 3;
continue;
}
if (lead >= 0xC2 && lead <= 0xDF && size - i >= 2 && (s[i + 1] & 0xC0u) == 0x80)
{
u_escape(((lead & 0x1Fu) << 6u) | (s[i + 1] & 0x3Fu));
i += 2;
continue;
}
const std::size_t len = validate_one_utf8(s + i, size - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
m_out.set_cursor(w);
invalid_utf8(s, size);
return; // LCOV_EXCL_LINE (invalid_utf8 throws)
}
std::uint32_t codepoint = s[i] & (0xFFu >> (len + 1));
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
i += len;
if (codepoint <= 0xFFFF)
{
u_escape(codepoint);
}
else
{
u_escape(0xD7C0u + (codepoint >> 10u));
u_escape(0xDC00u + (codepoint & 0x3FFu));
}
continue;
}
// a quote, a backslash, or a control character (or DEL)
const unsigned char c = s[i++];
char escape = 0;
switch (c)
{
case 0x08:
escape = 'b';
break;
case 0x09:
escape = 't';
break;
case 0x0A:
escape = 'n';
break;
case 0x0C:
escape = 'f';
break;
case 0x0D:
escape = 'r';
break;
case 0x22:
escape = '"';
break;
case 0x5C:
escape = '\\';
break;
default:
break;
}
if (escape != 0)
{
w[0] = '\\';
w[1] = escape;
w += 2;
}
else
{
u_escape(c);
}
}
room(1);
*w++ = '"';
m_out.set_cursor(w);
write_string(n);
}
/// the copies of dump_text() that are not fixed-size moves (long
/// 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
@@ -688,7 +481,7 @@ class view_serializer
}
/*!
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
@brief the compact output (the default dump(), or with ensure_ascii)
The same walk as dump(), with the write position in a local variable
(stores through char pointers would otherwise force a reload of the
@@ -696,11 +489,11 @@ class view_serializer
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. With ensure_ascii (Ascii), a
string of the source is copied only if it has nothing to escape (see
copied()); any other string is written by write_ascii().
string of the source is copied only if it has no byte >= 0x7F (see
is_ascii_without_delete()); any other string is written by write_string().
*/
template<bool SourceNumbers, bool Pretty, bool Ascii>
void dump_text(const node* root)
template<bool SourceNumbers, bool Ascii>
void dump_compact(const node* root)
{
struct frame
{
@@ -715,16 +508,6 @@ class view_serializer
const char* const src_end = src + m_doc.size;
char* w = m_out.cursor();
char* lim = m_out.limit();
// (indented output) a line break and 63 indentation characters, from
// which the line break of a level of up to 63 characters is a copy
std::array<char, 64> ruler; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled below if used
if (Pretty)
{
ruler.fill(m_style.indent_char);
ruler[0] = '\n';
}
const std::size_t indent = m_style.indent;
depth_counter<Pretty> level; // depth of the innermost open container (indented output)
// room for n bytes and the slack
const auto room = [&](std::size_t n)
{
@@ -756,22 +539,6 @@ class view_serializer
}
w += n;
};
// (indented output) the line break and the indentation of a level
const auto newline = [&](std::size_t depth)
{
const std::size_t count = 1 + (depth * indent);
room(count);
if (count <= ruler.size())
{
std::memcpy(w, ruler.data(), ruler.size());
}
else
{
*w = '\n';
std::memset(w + 1, m_style.indent_char, count - 1);
}
w += count;
};
// 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)
@@ -779,11 +546,11 @@ class view_serializer
m_out.set_cursor(w);
if (enabled(Ascii))
{
write_ascii(n);
write_ascii_string(n);
}
else
{
write_decoded(n, Pretty);
write_decoded(n);
}
w = m_out.cursor();
lim = m_out.limit();
@@ -884,7 +651,6 @@ class view_serializer
{
*w++ = object ? '{' : '[';
stack.push_back(cur);
level.enter();
if (Editable)
{
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
@@ -927,10 +693,6 @@ class view_serializer
{
break;
}
if (Pretty)
{
newline(level.leave());
}
room(1);
*w++ = cur.object ? '}' : ']';
cur = stack.back();
@@ -939,17 +701,13 @@ class view_serializer
room(1);
*w++ = ',';
}
if (Pretty)
{
newline(level.get());
}
const node* const at = Editable ? cur.pos : n;
if (cur.object)
{
const node& key = *at;
if (copied(key, src, std::integral_constant<bool, Ascii>()))
{
room(key.len + (Pretty ? 4 : 3));
room(key.len + 3);
*w++ = '"';
copy(src + key.off, key.len);
w[0] = '"';
@@ -959,13 +717,9 @@ class view_serializer
else
{
escaped(key);
room(Pretty ? 2 : 1);
room(1);
*w++ = ':';
}
if (Pretty)
{
*w++ = ' ';
}
if (Editable)
{
n = nav::value(at + 1);
@@ -984,6 +738,69 @@ class view_serializer
}
}
void newline(std::size_t level)
{
if (m_style.pretty)
{
m_out.put('\n');
m_out.put_repeated(m_style.indent_char, level * m_style.indent);
}
}
void write_scalar(const node& n)
{
switch (static_cast<value_t>(n.kind))
{
case value_t::null:
m_out.put("null");
break;
case value_t::boolean:
if ((n.flags & node_flags::is_true) != 0)
{
m_out.put("true");
}
else
{
m_out.put("false");
}
break;
case value_t::string:
write_string(n);
break;
case value_t::number_integer:
case value_t::number_unsigned:
{
const char* const token = m_doc.str(n);
const std::uint32_t len = number_length(n);
if (!m_style.source_numbers && len == 2 && token[0] == '-' && token[1] == '0')
{
m_out.put('0'); // parse() reads -0 as the integer 0
}
else
{
m_out.put(token, len);
}
break;
}
case value_t::number_float:
if (m_style.source_numbers && (n.flags & node_flags::storage) != node_flags::edited)
{
m_out.put(m_doc.str(n), n.len); // (a float set by an edit is written as with shortest)
}
else
{
write_float_node(n);
}
break;
case value_t::object: // LCOV_EXCL_LINE (containers are written by dump())
case value_t::array: // LCOV_EXCL_LINE
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
}
}
/// a float node as dump() writes it
void write_float_node(const node& n)
{
@@ -1073,23 +890,93 @@ class view_serializer
m_out.put("null");
return;
}
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
static constexpr bool is_ieee_single_or_double
= std::numeric_limits<number_float_t>::is_iec559
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
}
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
{
std::array<char, 64> buf{};
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
}
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
{
// other types (e.g. long double) are rare: the library writes them
const string_t s = BasicJsonType(x).dump();
m_out.put(s.data(), s.size());
}
void write_string(const node& n)
{
const char* const s = m_doc.str(n);
m_out.put('"');
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
{
// a string of the source without escape sequences has nothing to
// escape (with ensure_ascii, if it is printable ASCII)
m_out.put(s, n.len);
}
else if (m_style.ensure_ascii)
{
write_escaped<true>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
else
{
write_escaped<false>(reinterpret_cast<const unsigned char*>(s), n.len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
}
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
template<bool EnsureAscii>
void write_escaped(const unsigned char* s, std::size_t n)
{
std::size_t i = 0;
while (i < n)
{
std::size_t run = 0;
if (!EnsureAscii)
{
run = string_bulk_run(s + i, n - i);
}
else if (is_ascii_copyable(s[i]))
{
run = find_ascii_copyable_run(s + i, n - i);
}
if (run != 0)
{
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
i += run;
continue;
}
std::uint32_t codepoint = s[i];
std::size_t len = 1;
if (codepoint >= 0x80)
{
len = validate_one_utf8(s + i, n - i);
if (NLOHMANN_VIEW_UNLIKELY(len == 0))
{
invalid_utf8(s, n);
return;
}
codepoint &= 0xFFu >> (len + 1);
for (std::size_t k = 1; k < len; ++k)
{
codepoint = (codepoint << 6u) | (s[i + k] & 0x3Fu);
}
}
write_codepoint<EnsureAscii>(codepoint, s + i, len);
i += len;
}
}
/// 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)
{
@@ -1144,8 +1031,8 @@ class view_serializer
void write_u_escape(std::uint32_t u)
{
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
static constexpr const char* hex = "0123456789abcdef";
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
m_out.put(e.data(), e.size());
}
-28
View File
@@ -116,34 +116,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
}
return p;
}
/*!
@brief copy 16 bytes from p to w, and find the first quote, backslash,
control character, or non-ASCII byte among them
The bytes are copied whether or not they are all plain (the caller has room
for 16 bytes and keeps only the plain ones).
@return the offset of that byte, or 16 if there is none
*/
NLOHMANN_VIEW_ALWAYS_INLINE unsigned copy_plain_run16(const unsigned char* p, char* w) noexcept
{
#if NLOHMANN_VIEW_NEON
const uint8x16_t in = vld1q_u8(p);
vst1q_u8(reinterpret_cast<std::uint8_t*>(w), in); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits) >> 2u) : 16u;
#else
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(w)), in); // NOLINT(bugprone-casting-through-void)
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
return bits != 0 ? static_cast<unsigned>(count_trailing_zeros(bits)) : 16u;
#endif
}
#endif
#if NLOHMANN_VIEW_SSSE3_DISPATCH
+3 -3
View File
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
case value_t::binary:
case value_t::discarded:
default:
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
}
}
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
{
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
throw_type_error(302, "type must be array, but is ", v.type_name());
}
std::vector<U, A> r;
r.reserve(v.size());
@@ -88,7 +88,7 @@ Map map_value(const View& v)
{
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
{
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
throw_type_error(302, "type must be object, but is ", v.type_name());
}
Map r;
for (auto it = v.begin(); it != v.end(); ++it)
+401 -511
View File
File diff suppressed because it is too large. Load diff
+29 -103
View File
@@ -30,7 +30,6 @@
#include <array> // array
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
#include <cstdint> // uint8_t, uint32_t
#include <cstdio> // FILE
#include <cstring> // memcpy, strlen
#include <istream> // istream // IWYU pragma: keep
#include <iterator> // distance, input_iterator_tag, iterator_traits
@@ -268,7 +267,7 @@ class basic_json_view
{
return {};
}
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
}
return lookup(key);
}
@@ -294,9 +293,9 @@ class basic_json_view
{
return {};
}
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
}
return idx < m_node->len ? element(idx) : basic_json_view();
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
}
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
@@ -327,12 +326,12 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
}
const basic_json_view r = lookup(key);
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
{
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
}
return r;
}
@@ -353,13 +352,13 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
{
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
}
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
{
detail::view::throw_array_index_out_of_range(idx);
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
}
return element(idx);
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
/// any other integer type, see operator[]; negative values are out of
@@ -385,7 +384,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = lookup(key);
return r.is_discarded() ? default_value : r.template get<T>();
@@ -393,13 +392,7 @@ class basic_json_view
string_t value(string_view_t key, const char* default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
// (the default is made only if there is no such member)
const basic_json_view r = lookup(key);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
return value(key, string_t(default_value));
}
/// the value a JSON pointer refers to converted to T, or the default
@@ -410,7 +403,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? default_value : r.template get<T>();
@@ -418,12 +411,7 @@ class basic_json_view
string_t value(const json_pointer& ptr, const char* default_value) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
{
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
}
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
return value(ptr, string_t(default_value));
}
/// the first element or member value; a primitive value itself. Throws
@@ -433,7 +421,7 @@ class basic_json_view
const iterator it = begin();
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
{
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
detail::view::throw_invalid_iterator(214, "cannot get value");
}
return *it;
}
@@ -445,8 +433,7 @@ class basic_json_view
{
if (is_structured() && m_node->len != 0)
{
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
}
return front();
}
@@ -463,7 +450,7 @@ class basic_json_view
{
return end();
}
const node* const k = find_key(key);
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? iterator(m_doc, k, true) : end();
}
@@ -480,7 +467,7 @@ class basic_json_view
/// whether this is an object with a member with this key
bool contains(string_view_t key) const
{
return is_object() && find_key(key) != nullptr;
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
}
bool contains(const char* key) const
@@ -586,7 +573,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
{
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
}
return {m_doc->str(*m_node), m_node->len};
}
@@ -598,7 +585,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
{
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
}
@@ -640,14 +627,7 @@ class basic_json_view
// the compact writer keeps 64 bytes of slack, so that it does not grow
// the buffer just before the end
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
if (unedited())
{
detail::view::view_serializer<BasicJsonType, false>(*m_doc, out, estimate, style).dump(m_node);
}
else
{
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
}
detail::view::view_serializer<BasicJsonType, Editable>(*m_doc, out, estimate, style).dump(m_node);
return out;
}
@@ -727,7 +707,7 @@ class basic_json_view
{
return BasicJsonType(value_t::discarded);
}
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
}
/// byte offset of this value in the source text (for strings: of the
@@ -805,57 +785,17 @@ class basic_json_view
/// (object required)
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
{
if (unedited())
{
const node* const k = detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, k + 1) : basic_json_view();
}
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
return k != nullptr ? basic_json_view(m_doc, navigation::value(k + 1)) : basic_json_view();
}
/// whether the document has not been edited yet: until the first edit, its index is walked like that of a
/// read-only document (nothing is moved or linked), which is cheaper
NLOHMANN_VIEW_ALWAYS_INLINE bool unedited() const noexcept
{
return !navigation::editable() || m_doc->edits == nullptr;
}
/// the member of this object with @a key (the last one if the key occurs more than once), or nullptr
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key) const
{
return find_key(key, std::integral_constant<bool, Editable> {});
}
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::true_type /*editable*/) const
{
// (an editable document without edits is looked up as one that cannot be edited)
return unedited() ? detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size())
: detail::view::find_member<true>(*m_doc, m_node, key.data(), key.size());
}
NLOHMANN_VIEW_ALWAYS_INLINE const node* find_key(string_view_t key, std::false_type /*editable*/) const
{
return detail::view::find_member<false>(*m_doc, m_node, key.data(), key.size());
}
/// the element of an array at an index below its size
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view element(size_type idx) const
{
if (unedited())
{
return basic_json_view(m_doc, detail::view::element_at<false>(*m_doc, m_node, idx));
}
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
}
// --- get() dispatch ---
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
{
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
{
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
}
return (m_node->flags & detail::view::node_flags::is_true) != 0;
}
@@ -865,7 +805,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
{
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
}
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
}
@@ -874,7 +814,7 @@ class basic_json_view
{
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
{
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
}
return nullptr;
}
@@ -1183,7 +1123,7 @@ class basic_json_document
{
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
detail::view::throw_type_error(320, "cannot save a discarded json_document");
}
return detail::view::save_image(*m_data);
}
@@ -1284,7 +1224,7 @@ class basic_json_document
const std::size_t idx = pointer_index(token);
if (idx >= 0xFFFFFFFFu)
{
detail::view::throw_array_index_out_of_range(idx);
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
}
for (std::size_t i = 0; i < idx; ++i)
{
@@ -1340,7 +1280,7 @@ class basic_json_document
{
if (ptr.empty())
{
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
}
const view_type parent = root().at(ptr.parent_pointer());
const auto& token = ptr.back();
@@ -1360,7 +1300,7 @@ class basic_json_document
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
{
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
}
return detail::view::editor<BasicJsonType, view_type>(*m_data);
}
@@ -1377,7 +1317,7 @@ class basic_json_document
{
if (idx < 0)
{
detail::view::throw_array_index_out_of_range(idx);
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
}
return static_cast<std::size_t>(idx);
}
@@ -1588,8 +1528,7 @@ class basic_json_document
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
append_input_impl(buf, std::forward<T>(input), std::integral_constant < int, detail::is_contiguous_byte_container<D>::value ? 0
: std::is_base_of<std::istream, D>::value ? 1
: detail::view::is_adapter_input<D>::value ? 2
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
// NOLINTEND(readability-avoid-nested-conditional-operator)
}
@@ -1611,19 +1550,6 @@ class basic_json_document
detail::view::append_adapter(buf, std::move(input.ia));
}
#ifndef JSON_NO_IO
static void append_input_impl(std::string& buf, std::FILE* file, std::integral_constant<int, 4> /*C stream*/)
{
if (file == nullptr)
{
// (as before: the library's adapter decides what a null stream yields)
detail::view::append_adapter(buf, detail::input_adapter(file));
return;
}
detail::view::append_file(buf, file);
}
#endif
template<typename T>
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
{
+14 -15
View File
@@ -125,19 +125,6 @@ private:
Container::pop_back();
}
/// @brief remove the entry with the given key; returns the number of removed entries
template<class K>
size_type erase_impl(const K& key)
{
const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
}
public:
template<class V, detail::enable_if_t<
detail::is_constructible<T, V>::value, int> = 0>
@@ -227,14 +214,26 @@ public:
size_type erase(const key_type& key)
{
return erase_impl(key);
const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
}
template<class KeyType, detail::enable_if_t<
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
{
return erase_impl(key);
const auto it = find_impl(*this, key);
if (it != this->end())
{
erase_at(it);
return 1;
}
return 0;
}
iterator erase(iterator pos)
+2194 -5751
View File
File diff suppressed because it is too large. Load diff
+475 -941
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -60,7 +60,6 @@ inline namespace json_literals
namespace detail
{
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_parser;
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
} // namespace detail
-275
View File
@@ -492,278 +492,3 @@ TEST_CASE("binary_reserve_hint never over-reserves")
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
}
}
namespace
{
// numbers around every boundary of the integer encodings, and floats of each
// kind (exact in float, needing double, special values)
std::vector<json> boundary_numbers()
{
std::vector<json> numbers;
for (int k = 0; k < 64; ++k)
{
const std::uint64_t p = std::uint64_t{1} << k;
for (const std::uint64_t d :
{
p - 1, p, p + 1
})
{
numbers.emplace_back(d);
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
{
numbers.emplace_back(static_cast<std::int64_t>(d));
numbers.emplace_back(-static_cast<std::int64_t>(d));
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
}
}
}
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
for (const double f :
{
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::quiet_NaN()
})
{
numbers.emplace_back(f);
}
return numbers;
}
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
template<typename Single>
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
{
std::vector<std::uint8_t> bytes;
for (std::size_t i = first; i < first + count; ++i)
{
const auto one = single(numbers[i]);
bytes.insert(bytes.end(), one.begin(), one.end());
}
return bytes;
}
} // namespace
// Runs of numbers in arrays are encoded straight into space reserved from the
// sink, by code that is separate from the encoding of a single value. Both have
// to agree on every number: an array is its head and the encodings of its
// elements, one after the other.
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
{
const std::vector<json> numbers = boundary_numbers();
// arrays of every length that a one-byte head covers, so that runs are as
// long as, and longer and shorter than, what fits one reservation
for (const std::size_t count :
{
1u, 2u, 3u, 5u, 14u, 15u, 23u
})
{
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
{
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
auto elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_cbor(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
CHECK(json::to_cbor(array) == expected);
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
if (count > 15)
{
expected.push_back(0);
expected.push_back(static_cast<std::uint8_t>(count));
}
elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_msgpack(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
CHECK(json::to_msgpack(array) == expected);
for (const bool bjdata :
{
false, true
})
{
expected.assign(1, static_cast<std::uint8_t>('['));
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
{
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
expected.push_back(static_cast<std::uint8_t>(']'));
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
}
// BON8 has no integers above the int64 range
bool representable = true;
for (std::size_t i = first; i < first + count; ++i)
{
representable = representable && bon8_representable(numbers[i]);
}
if (representable)
{
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
elements = concat_singles(numbers, first, count, [](const json & j)
{
return json::to_bon8(j);
});
expected.insert(expected.end(), elements.begin(), elements.end());
if (count > 4)
{
expected.push_back(0xFE);
}
CHECK(json::to_bon8(array) == expected);
}
else
{
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
}
}
}
}
// An array that is written in more than one reservation (more numbers than fit
// one), and arrays of an index with more digits, round trip for every format.
TEST_CASE("large arrays and many digits of indices round trip")
{
json array = json::array();
for (int i = 0; i < 12000; ++i)
{
switch (i % 4)
{
case 0:
array.push_back(i);
break;
case 1:
array.push_back(-i);
break;
case 2:
array.push_back(i * 0.25);
break;
default:
array.push_back(i % 8 == 3);
break;
}
}
json document = json::object();
document["numbers"] = array;
document["nested"] = json::array({array, "text", array});
CHECK(json::from_cbor(json::to_cbor(document)) == document);
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
CHECK(json::from_bson(json::to_bson(document)) == document);
CHECK(json::from_bon8(json::to_bon8(document)) == document);
}
// Strings and keys are written with their head in one reservation up to a
// length; around that length, and with every kind of head, they must read back.
TEST_CASE("strings of every head length round trip")
{
json document = json::object();
json array = json::array();
for (const std::size_t length :
{
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
})
{
const std::string text(length, 'x');
array.push_back(text);
document[std::string(length, 'k')] = text;
}
document["array"] = array;
CHECK(json::from_cbor(json::to_cbor(document)) == document);
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
CHECK(json::from_bson(json::to_bson(document)) == document);
CHECK(json::from_bon8(json::to_bon8(document)) == document);
}
#if !defined(JSON_NOEXCEPTION)
// What a write that throws leaves behind in an output adapter is what was written
// before it, whatever the adapter and however the adapter sink buffers. (Without
// exceptions, the writes in CHECK_THROWS_AS do not run.)
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
{
json array = json::array();
for (int i = 0; i < 700; ++i)
{
array.push_back(i * 1000);
array.push_back("text " + std::to_string(i));
}
array.push_back("bad \xFF byte");
json document = json::object();
document["first"] = array;
document["second"] = 1;
const auto strict = json::error_handler_t::strict;
SECTION("cbor")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("msgpack")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("ubjson")
{
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
CHECK(!as_vector.empty());
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(as_stream.str() == as_string);
}
SECTION("bson")
{
// the size pass finds the string before anything is written
std::vector<std::uint8_t> as_vector;
std::string as_string;
std::ostringstream as_stream;
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
CHECK(as_vector.empty());
CHECK(as_string.empty());
CHECK(as_stream.str().empty());
}
}
#endif
-115
View File
@@ -2042,118 +2042,3 @@ TEST_CASE("string scanning kernels")
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
}
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
{
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
// second time; check every byte value at every position of a 24-byte buffer
// (three words) and for several lengths, with the byte at the end or inside
for (unsigned int value = 0; value < 256; ++value)
{
for (std::size_t position = 0; position < 24; ++position)
{
std::array<unsigned char, 24> buffer{};
buffer.fill(static_cast<unsigned char>('a'));
buffer[position] = static_cast<unsigned char>(value);
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
for (const std::size_t n :
{
position, position + 1, std::size_t{24}
})
{
if (n > 24)
{
continue;
}
const std::size_t expected = (copyable || n <= position) ? n : position;
CAPTURE(value)
CAPTURE(position)
CAPTURE(n)
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
}
}
}
}
TEST_CASE("UTF-8 validators accept the same sequences")
{
// The library checks UTF-8 in several places: detail::decode (serializer),
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
// They must accept exactly the same sequences (RFC 3629, section 4).
using nlohmann::json;
// bytes for the third and fourth position: the first two pairs are checked
// against all validators, the others only against the cheap ones
const std::array<std::array<unsigned char, 2>, 6> tails =
{
{
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
}
};
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
{
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
for (unsigned second = 0; second <= 0xFF; ++second)
{
for (std::size_t t = 0; t < tails.size(); ++t)
{
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
// expectation from the shape: the sequence is bytes[0..length)
const std::size_t length = shape.length;
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
for (std::size_t i = 2; expected && i < length; ++i)
{
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
}
// the DFA of the serializer
bool dfa = length >= 2;
if (dfa)
{
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
std::uint32_t codep = 0;
for (std::size_t i = 0; i < length; ++i)
{
nlohmann::detail::decode(state, codep, bytes[i]);
}
dfa = state == nlohmann::detail::UTF8_ACCEPT;
}
// the bulk path: exactly the sequence, and one byte short
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
CAPTURE(lead)
CAPTURE(second)
CAPTURE(t)
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
if (t < 2 && length >= 2)
{
// the lexer's byte path
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
CHECK(json::accept("\"" + sequence + "\"") == expected);
// the byte path of the BON8 reader (a string ends at 0xFF)
if (lead >= 0xC2 && lead <= 0xF7)
{
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
packed.push_back(0xFF);
const json j = json::from_bon8(packed, true, false);
if (expected)
{
CHECK(j == json(sequence));
}
else
{
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
}
}
}
}
}
}
}
-78
View File
@@ -2952,81 +2952,3 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
}
}
#endif
TEST_CASE("accept() agrees with parse() on floats around the overflow limit")
{
// accept() does not convert a float that cannot overflow; the tokens
// around the limit (and beyond it) must still be rejected exactly as
// parse() rejects them
const std::vector<const char*> numbers =
{
"1e0", "123.456e-5", "1e-400", "-1e-400", "1e307", "1e308", "1e309", "-1e309",
"10e307", "100e307", "0.1e309", "0.1e310", "1E+308", "1E+309",
"1.7976931348623157e308", "1.7976931348623159e308", "1.8e308", "-1.7976931348623157e308",
"12345678901234567e291", "12345678901234567e292", "123456789012345678e290", "123456789012345678e291",
"1234567890123456789e289", "1234567890123456789e290", "9999999999999999999e289", "9999999999999999999e290",
"0.00000000000000000001e328", "0.00000000000000000001e329"
};
for (const char* number : numbers)
{
for (const std::string& text :
{
std::string(number), "[" + std::string(number) + "]", "{\"a\":" + std::string(number) + "}"
})
{
INFO(text);
const bool parses = !json::parse(text, nullptr, false).is_discarded();
CHECK(json::accept(text) == parses);
}
}
}
namespace
{
// a DOM parser that reports parse errors without throwing, as in the sax_no_exception example
template<typename BasicJsonType>
class quiet_dom_parser : public nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>
{
public:
explicit quiet_dom_parser(BasicJsonType& j)
: nlohmann::detail::json_sax_dom_parser<BasicJsonType, nlohmann::detail::string_input_adapter_type>(j, false)
{}
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
{
return false;
}
};
template<typename BasicJsonType>
std::string read_before_error(const std::string& text)
{
BasicJsonType j;
quiet_dom_parser<BasicJsonType> sax(j);
CHECK(!BasicJsonType::sax_parse(text, &sax));
return j.dump();
}
} // namespace
TEST_CASE("a json_sax_dom_parser keeps the values read before a parse error")
{
// a json_sax_dom_parser that a user constructs stores every value in place, so a
// handler that does not throw on a parse error sees what was read before it
const std::vector<std::pair<std::string, std::string>> cases =
{
{"[1,2,3,]", "[1,2,3]"},
{"[[1,[2,3],4],[5,", "[[1,[2,3],4],[5]]"},
{R"({"a":1,"b":[1,2,{"c":)", R"({"a":1,"b":[1,2,{"c":null}]})"},
{R"({"a":1,"a":)", R"({"a":1})"},
{"[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,]", "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]"},
{R"({"k0":0,"k1":1,"k2":{"x":[true,)", R"({"k0":0,"k1":1,"k2":{"x":[true]}})"},
{R"([{},[],{"a":[]},x)", R"([{},[],{"a":[]}])"},
};
for (const auto& c : cases)
{
CAPTURE(c.first)
CHECK(read_before_error<json>(c.first) == c.second);
CHECK(read_before_error<nlohmann::ordered_json>(c.first) == c.second);
}
}
-52
View File
@@ -285,58 +285,6 @@ TEST_CASE("custom number types: integers beyond 64 bits")
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
}
SECTION("integers in arrays and objects")
{
// runs of numbers and scalar object members are encoded by loops of
// their own for the number types up to 64 bits; a wider number type
// takes the generic path, which checks the range of every integer
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
for (const auto& value : failing)
{
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
}
#if !defined(JSON_NOEXCEPTION)
// a failing write leaves no more than the elements before the failing one
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
std::vector<std::uint8_t> out;
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
out.clear();
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
CHECK(!out.empty());
#endif
// the same values that fit are written exactly as with 64-bit number types
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
}
SECTION("BJData ND-array")
{
// an ND-array element beyond 64 bits does not fit any dtype, so the
-127
View File
@@ -185,33 +185,6 @@ TEST_CASE("json_view")
// duplicate keys: the last value, at the position of the first key
CHECK(materialized<json_document>(R"({"a":1,"b":2,"a":3})") == json::parse(R"({"a":1,"b":2,"a":3})"));
CHECK(materialized<ordered_json_document>(R"({"a":1,"b":2,"a":3})").dump() == R"({"a":3,"b":2})");
// scalars (made without the handler), and nesting around the depth up to which the stack of open containers is local
for (const char* scalar :
{"null", "true", "false", "0", "-7", "18446744073709551615", "1.5e300", "-0.0", "\"\"", "\"short\"", "\"a string that is longer than fifteen bytes\"", R"("\u00e4\n")"
})
{
CAPTURE(scalar)
CHECK(materialized<json_document>(scalar) == json::parse(scalar));
const std::string in_array = std::string("[1,") + scalar + "]";
const json_document d = json_document::parse(in_array);
CHECK(d.root()[1].materialize() == json::parse(scalar));
CHECK(d.root()[1].get<json>() == json::parse(scalar));
}
for (const std::size_t levels :
{
std::size_t{1}, std::size_t{15}, std::size_t{16}, std::size_t{17}, std::size_t{32}, std::size_t{33}
})
{
CAPTURE(levels)
const std::string nested = std::string(levels, '[') + R"({"k":[1,{"j":2}]})" + std::string(levels, ']');
CHECK(materialized<json_document>(nested) == json::parse(nested));
std::string wide = R"({"a":)";
wide += nested;
wide += R"(,"b":)";
wide += nested;
wide += "}";
CHECK(materialized<json_document>(wide) == json::parse(wide));
}
// very deep nesting (iterative, as parse())
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
// (one check per step, so that a failure shows which step it is)
@@ -437,31 +410,6 @@ TEST_CASE("json_view")
std::istringstream rvalue_source("[1]");
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
// a C stream is read in blocks as well (more than one block, and a
// text whose length is a multiple of the block size)
for (const std::size_t extra :
{
std::size_t{0}, std::size_t{1}
})
{
std::FILE* long_file = std::tmpfile(); // NOLINT(cppcoreguidelines-owning-memory)
if (long_file != nullptr)
{
std::string padded = text;
padded.append(((4096 - (padded.size() % 4096)) % 4096) + extra, ' ');
CHECK(std::fwrite(padded.data(), 1, padded.size(), long_file) == padded.size());
CHECK(std::fseek(long_file, 0, SEEK_SET) == 0);
const json_document from_long_file = json_document::parse(long_file);
CHECK(from_long_file.source() == padded);
CHECK(from_long_file.root().materialize() == json::parse(text));
CHECK(std::fclose(long_file) == 0); // NOLINT(cppcoreguidelines-owning-memory)
}
}
{
json_document from_null;
CHECK_THROWS_AS(from_null = json_document::parse(static_cast<std::FILE*>(nullptr)), json::parse_error&);
}
// a stream without a buffer is an empty input
std::istream no_buffer(nullptr);
CHECK(json_document::parse(no_buffer, false).is_discarded());
@@ -1058,49 +1006,6 @@ TEST_CASE("json_view element access and iteration")
#endif
}
SECTION("arrays whose elements have no subtrees: index and back()")
{
// (the elements are adjacent in the index; an array, an object, or an
// empty container anywhere makes the lookup skip element by element)
const std::vector<std::string> texts =
{
"[]", "[1]", "[1,2,3]", "[1,\"two\",3.5,true,null,-7,18446744073709551615]", "[[]]", "[{}]", "[1,[2],3]",
"[1,2,{\"a\":3}]", "[[1,2],3,4]", "[1,2,3,[4,5,6,7,8],9,10,11,12]", "[{\"a\":[1]},2]", R"(["a\u00e4","b"])"
};
for (const std::string& text : texts)
{
CAPTURE(text)
const json_document d = json_document::parse(text);
const json_view v = d.root();
const json expected = json::parse(text);
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(v[i].materialize() == expected[i]);
CHECK(v.at(i).materialize() == expected[i]);
}
CHECK(v[expected.size()].is_discarded());
if (!expected.empty())
{
CHECK(v.back().materialize() == expected.back());
}
}
// a long array (the direct lookup is not limited to short ones), also with a container at its end
std::string text = "[0";
for (int i = 1; i < 1000; ++i)
{
text += "," + std::to_string(i);
}
const json_document scalars = json_document::parse(text + "]");
CHECK(scalars.root()[999].get<int>() == 999);
CHECK(scalars.root()[500].get<int>() == 500);
CHECK(scalars.root().at(1).get<int>() == 1);
CHECK(scalars.root().back().get<int>() == 999);
const json_document container_last = json_document::parse(text + ",[1000]]");
CHECK(container_last.root()[999].get<int>() == 999);
CHECK(container_last.root()[1000][0].get<int>() == 1000);
CHECK(container_last.root().back()[0].get<int>() == 1000);
}
SECTION("iterators")
{
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
@@ -1140,28 +1045,6 @@ TEST_CASE("json_view element access and iteration")
CHECK((*first++).key() == "0");
CHECK((*first).key() == "1");
CHECK(++first == items.end());
// the keys of array elements are their indexes, whether or not they are asked for
std::string numbers = "[0";
for (int i = 1; i < 1234; ++i)
{
numbers += "," + std::to_string(i);
}
const json_document array = json_document::parse(numbers + "]");
std::size_t position = 0;
for (const auto& item : array.root().items())
{
CHECK(item.value().get<std::size_t>() == position);
if (position % 3 != 0) // (not for every item: values alone do not make the key)
{
CHECK(item.key() == std::to_string(position));
CHECK(item.key().data() == item.key().data()); // (the same text every time)
const json_view::item copy = item;
CHECK(copy.key() == std::to_string(position));
}
++position;
}
CHECK(position == 1234);
#ifdef JSON_HAS_CPP_17
std::string pairs;
for (const auto [key, value] : d.root().items())
@@ -1500,17 +1383,7 @@ TEST_CASE("json_view values")
CHECK(exception_of([&] { static_cast<void>(v.value("s", 0)); }) == without_path(exception_of([&] { static_cast<void>(j.value("s", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", 0)); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value(json::json_pointer("/x"), 0)); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value(json::json_pointer("/x"), 0)); })));
// string defaults: the member is not a string, or the value is neither an object nor an array
CHECK(exception_of([&] { static_cast<void>(v.value("n", "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value("n", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["n"].value("x", "default")); }) == without_path(exception_of([&] { static_cast<void>(j["n"].value("x", "default")); })));
CHECK(exception_of([&] { static_cast<void>(v.value(json::json_pointer("/n"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j.value(json::json_pointer("/n"), "default")); })));
CHECK(exception_of([&] { static_cast<void>(v["s"].value(json::json_pointer("/x"), "default")); }) == without_path(exception_of([&] { static_cast<void>(j["s"].value(json::json_pointer("/x"), "default")); })));
#endif
// (defaults and members longer than the short string optimization)
CHECK(v.value("s", "a default longer than fifteen characters") == "text");
CHECK(v.value("missing", "a default longer than fifteen characters") == "a default longer than fifteen characters");
CHECK(v.value(json::json_pointer("/s"), "a default longer than fifteen characters") == "text");
CHECK(v.value(json::json_pointer("/zz"), "a default longer than fifteen characters") == "a default longer than fifteen characters");
}
}
-52
View File
@@ -523,58 +523,6 @@ TEST_CASE("json_view builder: strings across vector blocks")
}
}
TEST_CASE("json_view builder: escapes between runs of text")
{
// The text between escapes is copied 16 bytes at a time where there are
// that many bytes left, and with the UTF-8 check where there is
// non-ASCII text. Runs of every length from 0 to 40 between valid and
// invalid escapes, UTF-8 sequences, and control characters, in strings
// that are cut at all places near the end of the text.
const std::array<const char*, 9> valid = {{"\\n", "\\\"", "\\\\", "\\/", "\\t", "\\u00e4", "\\u20ac", "\\ud83d\\ude00", "\\u0000"}};
const std::array<const char*, 9> invalid = {{"\\x", "\\ud800", "\\udc00", "\\u12G4", "\\u12", "\\ud83dx", "\\ud83d\\u0041", "\\", "\x01"}};
const std::array<const char*, 6> utf8 = {{"\xc3\xa4", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xc3", "\xff", "\x80"}};
std::mt19937 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed): reproducible
for (std::size_t run = 0; run <= 40; ++run)
{
for (int i = 0; i < 60; ++i)
{
std::string content = std::string(rng() % 20, 'a');
const auto pieces = 1 + (rng() % 6);
for (unsigned k = 0; k < pieces; ++k)
{
content += valid[rng() % valid.size()];
content += std::string(k == 0 ? run : rng() % 41, static_cast<char>('b' + (rng() % 20)));
switch (rng() % 8)
{
case 0:
content += utf8[rng() % 3];
break;
case 1:
content += utf8[rng() % utf8.size()];
break;
case 2:
content += invalid[rng() % invalid.size()];
break;
default:
break;
}
}
check_string(content);
check_string(content.substr(0, content.size() - (rng() % 4)));
}
}
// the text before the first escape, and runs and escapes at the end of the text
for (std::size_t before = 0; before <= 40; ++before)
{
for (const char* escape : valid)
{
check_string(std::string(before, 'a') + escape);
check_string(std::string(before, 'a') + escape + std::string(before % 19, 'z'));
check_string(std::string(before, 'a') + escape + escape + std::string(before % 19, 'z'));
}
}
}
TEST_CASE("json_view node integer bits")
{
using nlohmann::detail::view::integer_bits;
-275
View File
@@ -21,7 +21,6 @@ using nlohmann::ordered_json_view;
using json_view_test::generator;
using json_view_test::has_duplicate_keys;
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
@@ -30,7 +29,6 @@ using json_view_test::has_duplicate_keys;
#include <iomanip>
#include <map>
#include <random>
#include <set>
#include <sstream>
#include <string>
#include <vector>
@@ -188,133 +186,6 @@ TEST_CASE("json_view dump")
CHECK(deep_document.root().dump() == deep);
}
SECTION("ensure_ascii: a character that needs an escape at each position of a string")
{
// string lengths around the width of the vector steps, in a string value, a key, and a value near the end of the text
for (const char* const special :
{
"\xC3\xA9", "\xE4\xB8\xAD", "\xF0\x9F\x98\x80", "\x7F", "\\n", "\\u0001", "\\\"", "\\\\"
})
{
for (std::size_t length = 0; length <= 40; ++length)
{
for (std::size_t position = 0; position <= length; ++position)
{
std::string content(length, 'a');
content.insert(position, special);
const std::array<std::string, 3> texts =
{
{
"\"" + content + "\"",
"[\"" + content + "\"]",
"{\"" + content + "\":1}",
}
};
for (const std::string& text : texts)
{
CAPTURE(text)
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
CHECK(d.root().dump() == j.dump());
}
}
}
}
}
SECTION("ensure_ascii: every character of the Basic Multilingual Plane and some beyond")
{
// (the two- and three-byte sequences, including the ranges that need a check: E0, ED, EF BF BF)
std::string text = "[";
std::size_t in_chunk = 0;
const auto append = [&](std::uint32_t cp)
{
if (cp < 0x80)
{
if (cp < 0x20 || cp == '"' || cp == '\\')
{
return;
}
text += static_cast<char>(cp);
}
else if (cp < 0x800)
{
text += static_cast<char>(0xC0 | (cp >> 6));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
else if (cp < 0x10000)
{
text += static_cast<char>(0xE0 | (cp >> 12));
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
else
{
text += static_cast<char>(0xF0 | (cp >> 18));
text += static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
text += static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
text += static_cast<char>(0x80 | (cp & 0x3F));
}
};
for (std::uint32_t cp = 0x20; cp < 0x10400; ++cp)
{
if (cp >= 0xD800 && cp < 0xE000)
{
continue;
}
if (in_chunk == 0)
{
text += text.size() > 1 ? ",\"" : "\"";
}
append(cp);
if (++in_chunk == 37)
{
text += "\"";
in_chunk = 0;
}
}
text += in_chunk != 0 ? "\"]" : "]";
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
CHECK(d.root().dump(2, ' ', true) == j.dump(2, ' ', true));
CHECK(d.root().dump() == j.dump());
CHECK(d.root().dump(2) == j.dump(2));
}
SECTION("deep nesting, indented")
{
// indentations of up to and beyond the width of the writer's fixed-size copy
for (const int depth :
{
1, 5, 20, 70, 300
})
{
std::string text;
for (int i = 0; i < depth; ++i)
{
text += i % 2 == 0 ? "[1," : R"({"k":2,"\u00e9":)";
}
text += "[]";
for (int i = depth - 1; i >= 0; --i)
{
text += i % 2 == 0 ? "]" : "}";
}
const ordered_json_document d = ordered_json_document::parse(text);
const ordered_json j = ordered_json::parse(text);
for (const int indent :
{
0, 1, 2, 3, 8
})
{
CHECK(d.root().dump(indent) == j.dump(indent));
CHECK(d.root().dump(indent, '\t') == j.dump(indent, '\t'));
}
}
}
SECTION("the output buffer of a small value is small")
{
// an escaped key after the value: its node lies in the arena, so the
@@ -727,152 +598,6 @@ TEST_CASE("json_view large objects")
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
}
SECTION("keys that differ in their last bytes are spread over the table")
{
// The hash must spread the usual families of keys (numbered names, hex
// strings, dates, short names): a key farther than index_max_displacement
// from its home slot would leave the object without a table. The keys
// are placed with linear probing, as the table does.
const auto max_displacement = [](const std::vector<std::string>& keys)
{
std::size_t slots = 16;
while (slots < 2 * keys.size())
{
slots *= 2;
}
std::vector<bool> used(slots, false);
std::size_t farthest = 0;
for (const std::string& key : keys)
{
std::size_t i = nlohmann::detail::conditional_static_cast<std::size_t>(nlohmann::detail::view::key_hash(key.data(), key.size())) & (slots - 1);
std::size_t distance = 0;
while (used[i])
{
i = (i + 1) & (slots - 1);
++distance;
}
used[i] = true;
farthest = (std::max)(farthest, distance);
}
return farthest;
};
// the keys of a printf-like pattern with one %d, %0Nd or %0Nx, formatted with a stream
// (a format string that is not a literal is a Flawfinder error)
const auto numbered = [](const char* format, int count, int step)
{
const std::string pattern(format);
const std::size_t percent = pattern.find('%');
std::size_t conversion = percent + 1;
std::size_t width = 0;
while (pattern[conversion] >= '0' && pattern[conversion] <= '9')
{
width = (width * 10) + static_cast<std::size_t>(pattern[conversion] - '0');
++conversion;
}
const bool hex = pattern[conversion] == 'x';
std::vector<std::string> keys;
for (int i = 0; i < count; ++i)
{
std::ostringstream key;
key << pattern.substr(0, percent) << std::setfill('0') << std::setw(static_cast<int>(width))
<< (hex ? std::hex : std::dec) << (i * step) << pattern.substr(conversion + 1);
keys.push_back(key.str());
}
return keys;
};
for (const char* format :
{"key%d", "%d", "item_%04d", "a%05d", "%03dxyz", "id%d", "user_%d", "%d_suffix", "0x%04x", "A%07d", "12345678%d", "1234567%d", "123456789%d",
"configuration_parameter_value_%04d_extra", "2026-01-%02dT00:00:00Z"
})
{
CAPTURE(format)
CHECK(max_displacement(numbered(format, std::string(format).find("%02d") != std::string::npos ? 60 : 1000, 1)) < nlohmann::detail::view::index_max_displacement); // NOLINT(abseil-string-find-str-contains)
CHECK(max_displacement(numbered(format, 600, 7)) < nlohmann::detail::view::index_max_displacement);
}
std::mt19937_64 rng(7); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
for (const std::size_t length :
{
std::size_t{1}, std::size_t{2}, std::size_t{3}, std::size_t{5}, std::size_t{7}, std::size_t{8}, std::size_t{9}, std::size_t{12}, std::size_t{16}, std::size_t{17}, std::size_t{33}
})
{
CAPTURE(length)
std::set<std::string> distinct;
// (there are 26 and 676 keys of one and two letters)
std::size_t wanted = 700;
if (length == 1)
{
wanted = 26;
}
else if (length == 2)
{
wanted = 500;
}
while (distinct.size() < wanted)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
distinct.insert(key);
}
CHECK(max_displacement(std::vector<std::string>(distinct.begin(), distinct.end())) < nlohmann::detail::view::index_max_displacement);
}
}
SECTION("keys of every length that differ in one byte")
{
// In a large object, a key is found by comparing its bytes with those of the member the
// table points to: up to 16 bytes as two overlapping words, longer ones with memcmp.
// Keys of every length from 0 to 40, and keys that differ from them in one byte.
std::mt19937_64 rng(2026); // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
std::vector<std::string> keys;
std::set<std::string> distinct;
for (std::size_t length = 0; length <= 40; ++length)
{
for (int copy = 0; copy < 5; ++copy)
{
std::string key(length, 'a');
for (char& c : key)
{
c = static_cast<char>('a' + (rng() % 26));
}
if (distinct.insert(key).second)
{
keys.push_back(key);
}
}
}
std::string text = "{";
for (std::size_t i = 0; i < keys.size(); ++i)
{
text += (i != 0 ? ",\"" : "\"") + keys[i] + "\":" + std::to_string(i);
}
text += "}";
REQUIRE(keys.size() >= 128); // (a table is built for 128 members)
const json_document d = json_document::parse(text);
const json_view v = d.root();
for (std::size_t i = 0; i < keys.size(); ++i)
{
CAPTURE(keys[i])
CHECK(v[keys[i]].get<std::size_t>() == i);
CHECK(v.at(keys[i]).get<std::size_t>() == i);
CHECK(v.contains(keys[i]));
for (std::size_t at = 0; at < keys[i].size(); ++at)
{
std::string other = keys[i];
other[at] = other[at] == 'z' ? 'y' : 'z';
CAPTURE(other)
CHECK(v.contains(other) == (distinct.count(other) != 0));
}
CHECK(v.contains(keys[i] + "a") == (distinct.count(keys[i] + "a") != 0));
if (!keys[i].empty())
{
CHECK(v.contains(keys[i].substr(1)) == (distinct.count(keys[i].substr(1)) != 0));
}
}
}
SECTION("shrink_to_fit releases the tables' spare capacity")
{
const auto make_text = [](int objects, int members)
-109
View File
@@ -661,115 +661,6 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
CHECK(d.root().materialize() == expected);
}
TEST_CASE("json_view edits: arrays whose elements have no subtrees")
{
// such an array is indexed directly while it is in its parsed layout; edits
// that replace elements by scalars keep it, others move it
json_editable_document d = json_editable_document::parse("[10,20,30,40]");
json expected = json::array({10, 20, 30, 40});
const auto check = [&]
{
CHECK(d.root().size() == expected.size());
for (std::size_t i = 0; i < expected.size(); ++i)
{
CHECK(d.root()[i].materialize() == expected[i]);
CHECK(d.root().at(i).materialize() == expected[i]);
}
CHECK(d.root()[expected.size()].is_discarded());
CHECK(d.root().back().materialize() == expected.back());
CHECK(d.root().dump() == expected.dump());
};
check();
d.set(d.root()[1], "x");
expected[1] = "x";
check();
d.set(d.root()[3], 2.5);
expected[3] = 2.5;
check();
d.set(d.root()[2], json::array({1, 2}));
expected[2] = json::array({1, 2});
check();
d.push_back(d.root(), 50);
expected.push_back(50);
check();
d.insert(d.root(), 0, true);
expected.insert(expected.begin(), true);
check();
d.erase(d.root(), 1);
expected.erase(expected.begin() + 1);
check();
d.set(d.root()[2], nullptr);
expected[2] = nullptr;
check();
}
TEST_CASE("json_view edits: materializing values nested deeper than the document was parsed")
{
// the stack of open containers of materialize() is sized from the nesting of the parsed text;
// a value added by an edit can be deeper than that (and than the stack that is local to the call)
json_editable_document d = json_editable_document::parse("[1,[2]]");
json expected = json::array({1, json::array({2})});
json nested = 3;
for (int level = 0; level < 40; ++level)
{
nested = json::array({nested, json{{"k", level}}});
}
d.push_back(d.root(), nested);
expected.push_back(nested);
CHECK(d.root().materialize() == expected);
CHECK(d.root()[2].materialize() == nested);
d.set(d.root()[1][0], nested);
expected[1][0] = nested;
CHECK(d.root().materialize() == expected);
CHECK(d.root().get<json>() == expected);
}
TEST_CASE("json_view edits: an editable document before and after its first edit")
{
// until the first edit, an editable document is read like a read-only one; views made before the edit
// keep working after it
const std::string text = R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"text","f":[]})";
const json_document read_only = json_document::parse(text);
json_editable_document d = json_editable_document::parse(text);
const auto root = d.root();
const auto same_as_read_only = [&]
{
CHECK(root.dump() == read_only.root().dump());
CHECK(root.dump(2) == read_only.root().dump(2));
CHECK(root.materialize() == read_only.root().materialize());
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].at(3).get<int>() == 4);
CHECK(root["a"].back().get<int>() == 4);
CHECK(root.contains("b"));
CHECK(!root.contains("zz"));
CHECK(root.find("e")->get_string() == "text");
CHECK(root.find("zz") == root.end());
CHECK(root["b"]["c"].get<bool>());
CHECK(root.value("e", "none") == "text");
std::size_t count = 0;
for (const auto m : root["a"])
{
CHECK(m.materialize() == read_only.root()["a"][count].materialize());
++count;
}
CHECK(count == 4);
};
same_as_read_only();
// an edit that changes a scalar in place, then one that moves an array
d.set(root["e"], "other");
CHECK(root["e"].get_string() == "other");
CHECK(root["a"][2]["x"].get_string() == "y");
CHECK(root["a"].back().get<int>() == 4);
d.push_back(root["f"], 7);
CHECK(root["f"][0].get<int>() == 7);
CHECK(root["f"].back().get<int>() == 7);
CHECK(root.dump() == R"({"a":[1,2,{"x":"y"},4],"b":{"c":true,"d":null},"e":"other","f":[7]})");
d.set(root["a"], 2, json::array({1, 2}));
CHECK(root["a"][2][1].get<int>() == 2);
CHECK(root["a"].dump() == "[1,2,[1,2],4]");
CHECK(root.materialize() == json::parse(R"({"a":[1,2,[1,2],4],"b":{"c":true,"d":null},"e":"other","f":[7]})"));
}
namespace
{
// reads of "a" in an object that repeats it: the first member wins, however the
-20
View File
@@ -682,26 +682,6 @@ TEST_CASE("json_view images: ownership")
#if !defined(JSON_NOEXCEPTION)
TEST_CASE("json_view images: errors")
{
SECTION("invalid UTF-8 of a decoded string: the compact dump writes it, the others throw")
{
const json_document d = json_document::parse(R"({"k\n":"\u00e9x"})");
std::vector<std::uint8_t> image = d.save();
const std::size_t arena = text_at(image) + header_size_field(image, 16) + 1;
image[arena + 3] = 0x28; // the key is "k\n"; the value starts at 3: 0xC3 0x28 is invalid
const json_document loaded = json_document::load(image, image_check::bounds);
CHECK(loaded.root().dump().size() == 13);
for (const int indent :
{
0, 2
})
{
CHECK_THROWS_AS(loaded.root().dump(indent), json::type_error);
CHECK_THROWS_AS(loaded.root().dump(indent, ' ', true), json::type_error);
}
CHECK_THROWS_AS(loaded.root().dump(-1, ' ', true), json::type_error);
CHECK_THROWS_WITH_AS(loaded.root().dump(2), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0x28", json::type_error&);
}
SECTION("a literal as the root: dump() after loading")
{
for (const char* text :
-278
View File
@@ -12,11 +12,6 @@
using nlohmann::json;
using nlohmann::ordered_json;
#include <array>
#include <cstddef>
#include <cstdint>
#include <string>
TEST_CASE("ordered_json")
{
json j;
@@ -242,276 +237,3 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
CHECK(oj.dump() == "{\"a\":1}");
}
}
namespace
{
// the sizes around which the DOM parser changes how it stores the elements of
// an array (16) and the members of an object in a vector (32, and 64 for the
// key index)
const std::array<std::size_t, 17> container_sizes = {{0, 1, 2, 3, 15, 16, 17, 18, 31, 32, 33, 34, 63, 64, 65, 66, 200}};
template<typename Json>
std::string array_text(const std::size_t n, const std::size_t depth)
{
std::string text = "[";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
// alternate scalars, empty containers and nested containers
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += "\"s" + std::to_string(i) + "\"";
break;
case 2:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
default:
text += depth == 0 ? "{}" : "{\"a\":" + array_text<Json>(n / 3, depth - 1) + ",\"b\":null}";
break;
}
}
return text + "]";
}
template<typename Json>
std::string object_text(const std::size_t n, const std::size_t depth, const bool duplicates)
{
std::string text = "{";
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"key" + std::to_string(i) + "\":";
switch (i % 4)
{
case 0:
text += std::to_string(i);
break;
case 1:
text += depth == 0 ? "[]" : array_text<Json>(n / 2, depth - 1);
break;
case 2:
text += depth == 0 ? "{}" : object_text<Json>(n / 2, depth - 1, duplicates);
break;
default:
text += "\"value\"";
break;
}
}
if (duplicates)
{
// keys that are there already, the first and the last one, with other values
text += n == 0 ? "" : ",";
text += R"("key0":[1,2],"key)";
text += std::to_string(n - 1);
text += R"(":{"again":true})";
}
return text + "}";
}
template<typename Json>
void check_same_dump(const std::string& text)
{
// the text is what dump() writes for the parsed value, and the value
// survives a second round trip: arrays keep their elements in order, an
// ordered_json keeps its members in order of appearance
const Json parsed = Json::parse(text);
const Json again = Json::parse(parsed.dump());
CHECK(parsed == again);
CHECK(parsed.dump() == again.dump());
CHECK(Json::accept(text));
}
} // namespace
TEST_CASE("parsing containers of sizes around the limits of the DOM parser")
{
SECTION("arrays")
{
for (const std::size_t n : container_sizes)
{
const std::string text = array_text<json>(n, 2);
INFO("size ", n);
const json parsed = json::parse(text);
CHECK(parsed.is_array());
CHECK(parsed.size() == n);
for (std::size_t i = 0; i < n; i += 4)
{
CHECK(parsed[i] == i);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
CHECK(ordered_json::parse(text).dump() == parsed.dump());
}
}
SECTION("objects")
{
for (const bool duplicates :
{
false, true
})
{
for (const std::size_t n : container_sizes)
{
const std::string text = object_text<json>(n, 2, duplicates);
INFO("size ", n, " duplicates ", duplicates);
const json parsed = json::parse(text);
const ordered_json ordered = ordered_json::parse(text);
CHECK(parsed.is_object());
CHECK(ordered.is_object());
CHECK(parsed.size() == ordered.size());
// the same members, in the order of their first appearance
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
if (i < n)
{
CHECK(it.key() == "key" + std::to_string(i));
}
CHECK(parsed.at(it.key()) == json(it.value()));
}
if (duplicates && n > 1)
{
// the later value replaced the earlier one in its place
CHECK(ordered.at("key0") == ordered_json::parse("[1,2]"));
CHECK(ordered.at("key" + std::to_string(n - 1)) == ordered_json::parse("{\"again\":true}"));
CHECK(ordered.begin().key() == "key0");
CHECK(ordered.size() == n);
}
check_same_dump<json>(text);
check_same_dump<ordered_json>(text);
}
}
}
SECTION("objects with many different and repeated keys")
{
// 300 members of which a third repeat an earlier key
std::string text = "{";
for (std::size_t i = 0; i < 300; ++i)
{
text += (i == 0 ? "" : ",");
text += "\"k" + std::to_string(i % 200) + "\":" + std::to_string(i);
}
text += '}';
const ordered_json ordered = ordered_json::parse(text);
const json parsed = json::parse(text);
CHECK(ordered.size() == 200);
CHECK(parsed.size() == 200);
std::size_t i = 0;
for (auto it = ordered.begin(); it != ordered.end(); ++it, ++i)
{
CHECK(it.key() == "k" + std::to_string(i));
// the last value written
CHECK(it.value() == (i < 100 ? i + 200 : i));
CHECK(parsed.at(it.key()) == json(it.value()));
}
}
}
namespace
{
// a deterministic source of structure for parsing tests: builds a JSON text
// and, at the same time, the value it has to parse to
class tree_generator
{
public:
explicit tree_generator(std::uint32_t seed) : state(seed) {}
template<typename Json>
Json make(std::string& text, const std::size_t depth)
{
switch (next(depth == 0 ? 5 : 9))
{
case 0:
text += "null";
return Json(nullptr);
case 1:
text += "true";
return Json(true);
case 2:
{
const int n = static_cast<int>(next(2000)) - 1000;
text += std::to_string(n);
return Json(n);
}
case 3:
text += "\"text\"";
return Json("text");
case 4:
text += "2.5";
return Json(2.5);
case 5:
case 6:
case 7:
{
Json array = Json::array();
text += '[';
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
text += (i == 0 ? "" : ",");
array.push_back(make<Json>(text, depth - 1));
}
text += ']';
return array;
}
default:
{
Json object = Json::object();
text += '{';
const std::size_t n = size();
for (std::size_t i = 0; i < n; ++i)
{
const std::string key = "k" + std::to_string(i);
text += (i == 0 ? "\"" : ",\"") + key + "\":";
object[key] = make<Json>(text, depth - 1);
}
text += '}';
return object;
}
}
}
private:
std::uint32_t next(const std::uint32_t bound)
{
state = (state * 1664525U) + 1013904223U;
return (state >> 8) % bound;
}
std::size_t size()
{
return container_sizes[next(static_cast<std::uint32_t>(container_sizes.size()))];
}
std::uint32_t state;
};
} // namespace
TEST_CASE("parsing random trees of arrays and objects")
{
for (std::uint32_t seed = 1; seed <= 150; ++seed)
{
INFO("seed ", seed);
std::string text;
std::string ordered_text;
const json expected = tree_generator(seed).make<json>(text, 3);
const auto ordered_expected = tree_generator(seed).make<ordered_json>(ordered_text, 3);
CHECK(text == ordered_text);
CHECK(json::parse(text) == expected);
CHECK(json::accept(text));
const ordered_json ordered = ordered_json::parse(text);
CHECK(ordered == ordered_expected);
CHECK(ordered.dump() == ordered_expected.dump());
CHECK(json::parse(text).dump() == expected.dump());
}
}
-91
View File
@@ -1580,94 +1580,3 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
}
}
}
TEST_CASE("short strings are written like the reference, as values and as keys")
{
// strings of up to 16 characters (32 with vector instructions) that need no
// escaping are written as a whole (together with the quotes and, for a key,
// the colon); everything else, including a string that is merely short,
// takes the general path.
// Every length around the edges of the size classes of the fast path is
// combined with each kind of byte that must send a string to the general
// path, at each position, and compared with the reference for values and,
// via the value, for keys.
const std::vector<std::string> specials =
{
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
};
for (std::size_t length = 0; length <= 36; ++length)
{
const std::string plain(length, 'k');
check_dump_against_reference(plain, false);
for (const auto& special : specials)
{
for (std::size_t pos = 0; pos <= length; ++pos)
{
std::string s = plain;
s.insert(pos, special);
check_dump_against_reference(s, false);
for (const bool ensure_ascii :
{
false, true
})
{
// a key and a value are written alike, in compact and
// in pretty-printed form
const json value = s;
const json object = json::object({{s, 1}});
for (const auto error_handler :
{
json::error_handler_t::replace, json::error_handler_t::keep
})
{
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
}
}
}
}
}
}
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
{
// a run of well-formed multi-byte characters is escaped straight into the
// write buffer; it has to flush that mid-run, stop at an ASCII character or
// an ill-formed sequence and leave those to the general path, and pick up
// behind what the general path has pending
const std::vector<std::string> characters =
{
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
};
const std::vector<std::string> interruptions =
{
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
};
for (std::size_t shift = 0; shift <= 13; ++shift)
{
for (const auto& interruption : interruptions)
{
for (const std::size_t count :
{
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
})
{
std::string s(shift, 'x');
for (std::size_t i = 0; i < count; ++i)
{
s += characters[(i + shift) % characters.size()];
}
s += interruption;
s += characters[shift % characters.size()];
s += characters[(shift + 3) % characters.size()];
check_dump_against_reference(s, shift < 2);
}
}
}
}
+1 -2
View File
@@ -78,8 +78,7 @@ TEST_CASE("detail::bit_cast")
// to and from byte arrays
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
// qualified: with a std::array argument, ADL would also find std::bit_cast in C++20
CHECK(nlohmann::detail::bit_cast<std::uint32_t>(bytes) == 0x01020304u);
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
}
+6 -34
View File
@@ -48,16 +48,14 @@ namespace
class chunked_streambuf : public std::streambuf
{
public:
// The first @a unbuffered characters are served without a get area, the
// rest in get areas of @a chunk_size.
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
chunked_streambuf(std::string s, std::size_t chunk_size)
: m_data(std::move(s)), m_chunk_size(chunk_size)
{}
protected:
int_type underflow() override
{
if (m_chunk_size == 0 || m_pos < m_unbuffered)
if (m_chunk_size == 0)
{
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
}
@@ -79,7 +77,7 @@ class chunked_streambuf : public std::streambuf
int_type uflow() override
{
if (m_chunk_size == 0 || m_pos < m_unbuffered)
if (m_chunk_size == 0)
{
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
}
@@ -90,7 +88,6 @@ class chunked_streambuf : public std::streambuf
std::string m_data;
std::size_t m_pos = 0;
std::size_t m_chunk_size;
std::size_t m_unbuffered;
};
// how a test reads from the stream
@@ -104,9 +101,9 @@ enum class read_mode
// Read @a input from a chunked_streambuf and describe everything observable:
// the values or the exception, the stream state after each value, and the
// characters left in the stream.
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
{
chunked_streambuf buf(input, chunk_size, unbuffered);
chunked_streambuf buf(input, chunk_size);
std::istream is(&buf);
std::string result;
for (int i = 0; i < 4; ++i)
@@ -215,31 +212,6 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
}
}
SECTION("results do not depend on a get area that appears late")
{
// the first characters come without a get area, then the streambuf
// starts to use one: the token that is read at that point is complete
for (const auto& input : corpus())
{
const std::string expected = observe(input, 0, read_mode::extract);
for (const std::size_t unbuffered :
{
1u, 2u, 3u, 5u, 8u, 13u, 21u
})
{
for (const std::size_t chunk_size :
{
1u, 2u, 3u, 7u, 64u
})
{
CAPTURE(input)
CAPTURE(unbuffered)
CAPTURE(chunk_size)
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
}
}
}
}
#endif // !defined(JSON_NOEXCEPTION)
SECTION("values are the same as when parsing from a string")
-3
View File
@@ -139,12 +139,9 @@ if __name__ == '__main__':
namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces]
# The template renders a local XML file, not a web response, and autoescape is enabled.
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True,
lstrip_blocks=True, keep_trailing_newline=True)
template = env.get_template('nlohmann_json.natvis.j2')
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
natvis = template.render(namespaces=namespaces)
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')