mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 14:58:54 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9c0fda829 | ||
|
|
a763c9eb83 | ||
|
|
9c50bdd140 | ||
|
|
dd7686a269 | ||
|
|
e9c4fe3cdf | ||
|
|
1e9222f955 | ||
|
|
ffcd392be0 | ||
|
|
e28506dd86 | ||
|
|
03a0595c1e | ||
|
|
4e72af9d54 | ||
|
|
0f5d07310c | ||
|
|
0b924f5375 | ||
|
|
6fa14d8f95 | ||
|
|
dec0e9b749 | ||
|
|
86d5c56c54 | ||
|
|
aa0b371f7c | ||
|
|
5c2aba70f9 | ||
|
|
ace1030107 | ||
|
|
510ee0ab4f | ||
|
|
5da0e9181f | ||
|
|
0aff24ca37 | ||
|
|
c25aad3ca5 | ||
|
|
6a0cf2ba2f | ||
|
|
24de5b2f5c | ||
|
|
fb729028ba | ||
|
|
c9fdfc39db | ||
|
|
870bdec01f | ||
|
|
effa51d528 | ||
|
|
545477ffa8 | ||
|
|
aeba637593 | ||
|
|
466e56d92d | ||
|
|
b7b75aac8a | ||
|
|
516b9b516a | ||
|
|
1d2dea7769 | ||
|
|
f8ff0942b2 | ||
|
|
d1e6439187 | ||
|
|
dcf7e2222c | ||
|
|
d99932fb3f | ||
|
|
1d2cb49ff3 | ||
|
|
a2766df787 | ||
|
|
2ea5bed0e3 | ||
|
|
632f5369fe | ||
|
|
c2619bf460 | ||
|
|
351aeb7440 | ||
|
|
7b0af0072e | ||
|
|
650886d126 | ||
|
|
a759afc99f | ||
|
|
1143da4faa | ||
|
|
8de151f928 | ||
|
|
ab52c98f71 | ||
|
|
5e93415d91 | ||
|
|
7e8e8e219b | ||
|
|
5f1727cef2 | ||
|
|
c16dd7e4f5 | ||
|
|
792853d725 | ||
|
|
4bdf1b7e74 | ||
|
|
b1e9d98e41 | ||
|
|
f855d257df | ||
|
|
3e683e9c04 | ||
|
|
d1d84ed9af | ||
|
|
de8529f99b | ||
|
|
677794f076 | ||
|
|
437a95cfdb | ||
|
|
c8735246d0 | ||
|
|
9adb510a0d |
No files matched your search
@@ -111,6 +111,8 @@ context which existing file needs to be extended, and only very few cases requir
|
|||||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||||
|
Tests that call `sax_parse` go into `unit-sax_parse.cpp` rather than into a large test file: every call instantiates
|
||||||
|
the parser and the binary reader that recover from errors, which grows a test file considerably.
|
||||||
|
|
||||||
#### Exceptions
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
@@ -37,6 +37,14 @@ jobs:
|
|||||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||||
return artifact.name == "pr"
|
return artifact.name == "pr"
|
||||||
})[0];
|
})[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({
|
var download = await github.rest.actions.downloadArtifact({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
@@ -51,9 +59,11 @@ jobs:
|
|||||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||||
# read only the two expected files by fixed path afterwards. This avoids a
|
# read only the two expected files by fixed path afterwards. This avoids a
|
||||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||||
- run: unzip -o pr.zip -d ./pr_artifact
|
- if: steps.download.outputs.found == 'true'
|
||||||
|
run: unzip -o pr.zip -d ./pr_artifact
|
||||||
|
|
||||||
- name: 'Comment on PR'
|
- name: 'Comment on PR'
|
||||||
|
if: steps.download.outputs.found == 'true'
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ jobs:
|
|||||||
- name: Summary
|
- name: Summary
|
||||||
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
with:
|
with:
|
||||||
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||||
path: tests/benchmarks/json_view/results/
|
path: tests/benchmarks/json_view/results/
|
||||||
@@ -80,6 +80,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
@@ -495,7 +495,7 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed.
|
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||||
|
|
||||||
### STL-like access
|
### STL-like access
|
||||||
|
|
||||||
|
|||||||
@@ -341,13 +341,14 @@ def download(url, docs) -> str:
|
|||||||
raise ValueError(f'not an http(s) URL: {url}')
|
raise ValueError(f'not an http(s) URL: {url}')
|
||||||
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
||||||
# (the scheme is checked above)
|
# (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
|
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
||||||
data = r.read()
|
data = r.read()
|
||||||
ctype = r.headers.get_content_type()
|
ctype = r.headers.get_content_type()
|
||||||
path = urllib.parse.unquote(u.path).lstrip('/')
|
path = urllib.parse.unquote(u.path).lstrip('/')
|
||||||
ext = os.path.splitext(path)[1]
|
ext = os.path.splitext(path)[1]
|
||||||
if u.query or not ext or path.endswith('/'):
|
if u.query or not ext or path.endswith('/'):
|
||||||
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
|
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
|
||||||
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
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))
|
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
||||||
out = os.path.join(docs, rel)
|
out = os.path.join(docs, rel)
|
||||||
|
|||||||
@@ -23,9 +23,6 @@ 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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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
|
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.
|
(i.e., a parse error) nor creates diagnostic information.
|
||||||
|
|
||||||
@@ -38,12 +35,9 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
|
|||||||
- a `#!c FILE` pointer (throws if null)
|
- a `#!c FILE` pointer (throws if null)
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
- a pointer to a null-terminated string of single byte characters (throws if null)
|
||||||
- a `std::string` or a `std::string_view` (C++17)
|
- a `std::string`
|
||||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type, for instance.
|
: a compatible iterator type, for instance.
|
||||||
@@ -131,21 +125,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
--8<-- "examples/accept__iterator_pair.output"
|
--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
|
## See also
|
||||||
|
|
||||||
- [parse](parse.md) - deserialize from a compatible input
|
- [parse](parse.md) - deserialize from a compatible input
|
||||||
@@ -172,9 +151,6 @@ 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)).
|
([`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
|
- 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)).
|
([`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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -44,22 +44,6 @@ are collected in [Template Parameter Requirements](../../features/types/template
|
|||||||
- [**json**](../json.md) - default specialization
|
- [**json**](../json.md) - default specialization
|
||||||
- [**ordered_json**](../ordered_json.md) - a specialization that maintains the insertion order of object keys
|
- [**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
|
## Iterator invalidation
|
||||||
|
|
||||||
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
|
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
|
||||||
@@ -359,9 +343,6 @@ Access to the JSON value
|
|||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259)
|
- [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
|
## Version history
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,6 @@ 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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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
|
## Template parameters
|
||||||
|
|
||||||
`InputType`
|
`InputType`
|
||||||
@@ -38,12 +34,9 @@ static basic_json parse(IteratorType first, SentinelType last,
|
|||||||
- a `FILE` pointer (throws if null)
|
- a `FILE` pointer (throws if null)
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
- a pointer to a null-terminated string of single byte characters (throws if null)
|
||||||
- a `std::string` or a `std::string_view` (C++17)
|
- a `std::string`
|
||||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type, for instance.
|
: a compatible iterator type, for instance.
|
||||||
@@ -119,13 +112,6 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
|
|||||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||||
|
|
||||||
!!! tip "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"
|
!!! tip "Performance"
|
||||||
|
|
||||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
||||||
@@ -264,22 +250,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
--8<-- "examples/trailing_commas.output"
|
--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
|
## See also
|
||||||
|
|
||||||
- [accept](accept.md) - check if the input is valid JSON
|
- [accept](accept.md) - check if the input is valid JSON
|
||||||
@@ -310,9 +280,6 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
|||||||
time).
|
time).
|
||||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
- 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`).
|
([`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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ 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
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
the iterator type with `operator!=`.
|
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).
|
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
@@ -46,12 +42,8 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
- a `FILE` pointer
|
- a `FILE` pointer
|
||||||
- a C-style array of characters
|
- a C-style array of characters
|
||||||
- a pointer to a null-terminated string of single byte 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
|
- 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`)
|
(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`
|
`IteratorType`
|
||||||
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
||||||
@@ -104,7 +96,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
return value of the last processed SAX event
|
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||||
|
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
||||||
|
(see [error recovery](../../features/parsing/error_recovery.md)).
|
||||||
|
|
||||||
## Exception safety
|
## Exception safety
|
||||||
|
|
||||||
@@ -161,6 +155,7 @@ use the same file with stdio functions, but it must not wait for another thread
|
|||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Added `tag_handler` in version 3.13.0.
|
- Added `tag_handler` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||||
after the parsed value when `strict` is `#!cpp false`.
|
after the parsed value when `strict` is `#!cpp false`.
|
||||||
@@ -168,9 +163,6 @@ 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)).
|
([`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
|
- 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)).
|
([`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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ class basic_json_document;
|
|||||||
|
|
||||||
A parsed JSON text, held as a flat index of its values
|
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.
|
([16 bytes per value](../../home/architecture.md#node-index-of-json-views)) instead of a tree of `BasicJsonType` values.
|
||||||
Strings and floating-point numbers stay in the source text; integers are converted while parsing, and strings that
|
Strings and numbers stay in the source text; only strings that contain escapes are decoded, into one buffer owned by
|
||||||
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
|
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
|
`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.
|
`BasicJsonType` value that [`BasicJsonType::parse()`](../basic_json/parse.md) would have produced for it.
|
||||||
|
|
||||||
@@ -115,12 +115,6 @@ points apply to every edit:
|
|||||||
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
|
[`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.
|
`#!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
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- Added in version 3.13.0.
|
||||||
@@ -21,19 +21,17 @@ 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
|
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
|
`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 accept every input `BasicJsonType::parse()` takes, and in
|
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`. Byte
|
||||||
addition a pointer to a null-terminated wide string (which `BasicJsonType::parse()` rejects). Contiguous inputs of
|
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous
|
||||||
single-byte characters that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, which
|
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an
|
||||||
must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns -- see the
|
owned copy of a borrowable input as well.
|
||||||
Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input
|
|
||||||
as well.
|
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
`InputType`
|
`InputType`
|
||||||
: A compatible input, for instance:
|
: A compatible input, for instance:
|
||||||
|
|
||||||
- a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), or a C-style array of single-byte characters
|
- a `#!cpp std::string`, `#!cpp std::string_view`, 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`,
|
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
|
||||||
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
|
`#!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.
|
- a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g.
|
||||||
@@ -49,10 +47,6 @@ as well.
|
|||||||
`#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs
|
`#!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
|
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
|
## Parameters
|
||||||
|
|
||||||
`input` (in)
|
`input` (in)
|
||||||
@@ -161,5 +155,3 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- 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`.
|
|
||||||
@@ -80,7 +80,8 @@ 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
|
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.
|
constant time on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||||
|
or object, since the elements are then adjacent in the index.
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level (as 1.) or the index into the array (as 2.).
|
that level (as 1.) or the index into the array (as 2.).
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ 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
|
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
|
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
|
||||||
index.
|
index. Constant if no element of the array is an array or object, since the elements are then adjacent
|
||||||
|
in the index.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@@ -143,12 +143,6 @@ 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
|
- [**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
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.13.0.
|
- Added in version 3.13.0.
|
||||||
@@ -86,7 +86,8 @@ 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
|
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.
|
constant time on average.
|
||||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
index (unlike `BasicJsonType`'s array, which is random-access). Constant if no element of the array is an array
|
||||||
|
or object, since the elements are then adjacent in the index.
|
||||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||||
that level (as 1.) or the index into the array (as 2.).
|
that level (as 1.) or the index into the array (as 2.).
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ struct json_sax;
|
|||||||
|
|
||||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
||||||
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||||
processing the input.
|
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||||
|
[recover from the error](../../features/parsing/error_recovery.md).
|
||||||
|
|
||||||
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,18 @@ A parse error occurred.
|
|||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
Whether to recover from the error:
|
||||||
|
|
||||||
|
- `#!cpp false` stops parsing.
|
||||||
|
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
||||||
|
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
||||||
|
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
||||||
|
|
||||||
|
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example
|
??? example "Example: (1) the SAX interface"
|
||||||
|
|
||||||
The example below shows how the SAX interface is used.
|
The example below shows how the SAX interface is used.
|
||||||
|
|
||||||
@@ -39,12 +46,29 @@ Whether parsing should proceed (**must return `#!cpp false`**).
|
|||||||
--8<-- "examples/sax_parse.output"
|
--8<-- "examples/sax_parse.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? example "Example: (2) recovering from errors"
|
||||||
|
|
||||||
|
The example below shows how a SAX parser recovers from errors.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.output"
|
||||||
|
```
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
||||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
||||||
exceptions
|
exceptions
|
||||||
|
- [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors
|
||||||
|
|
||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.2.0.
|
- Added in version 3.2.0.
|
||||||
|
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
||||||
|
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#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';
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
true false
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#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';
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
{"name":"é"}
|
|
||||||
{"name":"é"}
|
|
||||||
{"name":"é"}
|
|
||||||
{"name":"é"}
|
|
||||||
[1,2,3]
|
|
||||||
{"name":"é"}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// a SAX parser that creates a JSON value like json::parse does, but that
|
||||||
|
// recovers from parse errors instead of stopping at the first one
|
||||||
|
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit recovering_parser(json& result)
|
||||||
|
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t position,
|
||||||
|
const std::string& /*last_token*/,
|
||||||
|
const json::exception& ex)
|
||||||
|
{
|
||||||
|
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
||||||
|
|
||||||
|
// repair the input and continue
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// JSON text with several mistakes that ends too early
|
||||||
|
const std::string text = R"({
|
||||||
|
"name": "Hello World",
|
||||||
|
"tags": ["a" "b",],
|
||||||
|
"valid": tru,
|
||||||
|
"size": 1.,
|
||||||
|
"nested": {"x": 1)";
|
||||||
|
|
||||||
|
json result;
|
||||||
|
recovering_parser sax(result);
|
||||||
|
const bool valid = json::sax_parse(text, &sax);
|
||||||
|
|
||||||
|
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
||||||
|
<< std::setw(4) << result << std::endl;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
||||||
|
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||||
|
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
||||||
|
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
||||||
|
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
||||||
|
|
||||||
|
valid JSON: false
|
||||||
|
{
|
||||||
|
"name": "Hello World",
|
||||||
|
"nested": {
|
||||||
|
"x": 1
|
||||||
|
},
|
||||||
|
"size": 1,
|
||||||
|
"tags": [
|
||||||
|
"a",
|
||||||
|
"b"
|
||||||
|
],
|
||||||
|
"valid": null
|
||||||
|
}
|
||||||
@@ -38,18 +38,6 @@ which avoids repeating it:
|
|||||||
--8<-- "examples/get_to.output"
|
--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
|
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
|
`#!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:
|
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
|
||||||
@@ -246,4 +234,3 @@ 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
|
- [`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
|
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
||||||
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|
- [`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
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
# Creating JSON values
|
# 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
|
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). A JSON text can also be parsed into a
|
instead, see [parsing](parsing/index.md).
|
||||||
[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
|
## From C++ values
|
||||||
|
|
||||||
@@ -103,4 +101,3 @@ string `#!json "42"`.
|
|||||||
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
||||||
- [Converting values](conversions.md) - which C++ types can be used
|
- [Converting values](conversions.md) - which C++ types can be used
|
||||||
- [Parsing](parsing/index.md) - create a value from JSON text
|
- [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
|
|
||||||
@@ -287,23 +287,6 @@ external dependency and is not defined by default.
|
|||||||
|
|
||||||
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
|
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_*(...)`
|
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
|
||||||
|
|
||||||
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
||||||
|
|||||||
@@ -85,18 +85,6 @@ json a = {1, 2, 3, 4};
|
|||||||
a.erase(1); // [1,3,4] (erase by index)
|
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
|
## See also
|
||||||
|
|
||||||
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
|
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
|
||||||
@@ -105,4 +93,3 @@ editable document types, such as [`json_editable_document`](../api/json_editable
|
|||||||
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
|
- [`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
|
- [`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
|
- [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
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Error Recovery
|
||||||
|
|
||||||
|
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
||||||
|
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
||||||
|
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
||||||
|
|
||||||
|
## Recovering from errors
|
||||||
|
|
||||||
|
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
||||||
|
decides what happens next:
|
||||||
|
|
||||||
|
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
||||||
|
and [`accept`](../../api/basic_json/accept.md) never recover.
|
||||||
|
- `#!cpp true` repairs the error and continues parsing.
|
||||||
|
|
||||||
|
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
||||||
|
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
||||||
|
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
||||||
|
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
||||||
|
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
||||||
|
`#!cpp false`.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
|
||||||
|
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
||||||
|
and recovers from all errors.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
--8<-- "examples/sax_parse__error_recovery.output"
|
||||||
|
```
|
||||||
|
|
||||||
|
## How errors are repaired
|
||||||
|
|
||||||
|
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
||||||
|
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
||||||
|
|
||||||
|
| Mistake | Repair | Example | Result |
|
||||||
|
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
||||||
|
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
||||||
|
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
||||||
|
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
||||||
|
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
||||||
|
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
||||||
|
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
||||||
|
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
||||||
|
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
||||||
|
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
||||||
|
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
||||||
|
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
||||||
|
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
||||||
|
|
||||||
|
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
||||||
|
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
||||||
|
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
||||||
|
|
||||||
|
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
||||||
|
|
||||||
|
## Binary formats
|
||||||
|
|
||||||
|
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
||||||
|
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
||||||
|
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
||||||
|
on whether the end of the item with the error is known, a distinction that
|
||||||
|
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
||||||
|
|
||||||
|
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
||||||
|
|
||||||
|
| Mistake | Formats | Repair |
|
||||||
|
|---------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------|
|
||||||
|
| tag, if `tag_handler` is `cbor_tag_handler_t::error` (the default) | CBOR | ignored |
|
||||||
|
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
||||||
|
| number too large for a custom `number_float_t`, like `float` | all | infinity |
|
||||||
|
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
||||||
|
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
||||||
|
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
||||||
|
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
||||||
|
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
||||||
|
| string without its terminator | BSON | kept |
|
||||||
|
| document whose size does not match its content | BSON | kept |
|
||||||
|
|
||||||
|
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
||||||
|
suggests for converting CBOR to JSON. By default, [`sax_parse`](../../api/basic_json/sax_parse.md) reports every tag as
|
||||||
|
an error; when recovering, tags are then ignored like with
|
||||||
|
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md). Strings that are not valid UTF-8 are no
|
||||||
|
error: like [`from_cbor`](../../api/basic_json/from_cbor.md) and the other functions by default, `sax_parse` passes
|
||||||
|
them on as they are.
|
||||||
|
|
||||||
|
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
||||||
|
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
||||||
|
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
||||||
|
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
||||||
|
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
||||||
|
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
||||||
|
reported errors.
|
||||||
|
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
||||||
|
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
||||||
|
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
||||||
|
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
||||||
|
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
||||||
|
BSON document after one whose end is unknown.
|
||||||
|
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
||||||
|
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
||||||
|
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
||||||
|
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
||||||
|
input after the value, for instance of the next value in a stream of concatenated values.
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
|
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
||||||
|
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||||
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
@@ -18,7 +18,6 @@ flowchart LR
|
|||||||
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
|
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 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 std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
|
||||||
- a `#!cpp FILE*` pointer,
|
- a `#!cpp FILE*` pointer,
|
||||||
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
|
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
|
||||||
@@ -33,18 +32,12 @@ std::ifstream f("example.json");
|
|||||||
json data = json::parse(f);
|
json data = json::parse(f);
|
||||||
```
|
```
|
||||||
|
|
||||||
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
|
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
|
||||||
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.
|
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`
|
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).
|
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
|
## Strictness and trailing data
|
||||||
|
|
||||||
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
||||||
@@ -105,42 +98,21 @@ The library offers two parsing models:
|
|||||||
You can influence a DOM parse without switching to the SAX interface by passing a
|
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.
|
[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
|
## Exceptions
|
||||||
|
|
||||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||||
options.
|
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
|
- [`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
|
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
|
||||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||||
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
||||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||||
|
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
||||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
- [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
|
|
||||||
@@ -64,7 +64,8 @@ bool parse_error(std::size_t position,
|
|||||||
const json::exception& ex);
|
const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||||
|
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||||
|
|
||||||
??? example "Example: report parse errors without exceptions"
|
??? example "Example: report parse errors without exceptions"
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ bool key(string_t& val);
|
|||||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||||
```
|
```
|
||||||
|
|
||||||
The return value of each function determines whether parsing should proceed.
|
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||||
|
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||||
|
|
||||||
To implement your own SAX handler, proceed as follows:
|
To implement your own SAX handler, proceed as follows:
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ To implement your own SAX handler, proceed as follows:
|
|||||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||||
|
|
||||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
|||||||
@@ -81,9 +81,6 @@ 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
|
- 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").
|
[`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
|
- In a binary format, a string that is not valid UTF-8 is a
|
||||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
||||||
|
|
||||||
|
|||||||
@@ -339,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
|||||||
- `output_stream_adapter` writes to a `std::ostream`.
|
- `output_stream_adapter` writes to a `std::ostream`.
|
||||||
- `output_string_adapter` appends to a string.
|
- `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
|
## Value conversion
|
||||||
|
|
||||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Debugging
|
# Debugging
|
||||||
|
|
||||||
This page collects the library's built-in debugger integrations and other debugging-related features. They are not
|
This page collects the library's built-in debugger integrations and other debugging-related features. They are
|
||||||
covered by any single page elsewhere in the docs, so they are collected here.
|
not linked from a single place elsewhere in the docs, so are collected here.
|
||||||
|
|
||||||
## Visual Studio (natvis)
|
## Visual Studio (natvis)
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,7 @@ 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.
|
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, and
|
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information.
|
||||||
[Debugging](debugging.md) for the debugger integrations of the library.
|
|
||||||
|
|
||||||
## Parse errors
|
## Parse errors
|
||||||
|
|
||||||
|
|||||||
@@ -81,9 +81,8 @@ rejected by default, and the library never adds them when serializing.
|
|||||||
|
|
||||||
The library supports **Unicode input** as follows:
|
The library supports **Unicode input** as follows:
|
||||||
|
|
||||||
- **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).
|
- 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; `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.
|
- `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.
|
||||||
- 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.
|
- 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).
|
- 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.
|
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||||
@@ -147,7 +146,7 @@ json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control
|
|||||||
|
|
||||||
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
|
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
!!! example
|
!!! example
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ 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
|
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
|
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
|
complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
|
||||||
down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
|
down. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
|
||||||
`<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
|
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
|
||||||
OSS-Fuzz.
|
OSS-Fuzz.
|
||||||
|
|
||||||
@@ -60,8 +58,7 @@ Conda, and more), and pkg-config.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
|
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more.
|
||||||
and more.
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: Features](features/index.md)
|
[:octicons-arrow-right-24: Features](features/index.md)
|
||||||
|
|
||||||
@@ -77,8 +74,7 @@ Conda, and more), and pkg-config.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
|
The complete reference for `basic_json` and its member functions, types, and related classes.
|
||||||
`json_document` and `json_view`.
|
|
||||||
|
|
||||||
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
|
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ nav:
|
|||||||
- features/performance.md
|
- features/performance.md
|
||||||
- Parsing:
|
- Parsing:
|
||||||
- features/parsing/index.md
|
- features/parsing/index.md
|
||||||
|
- features/parsing/error_recovery.md
|
||||||
- features/parsing/json_lines.md
|
- features/parsing/json_lines.md
|
||||||
- features/parsing/parse_exceptions.md
|
- features/parsing/parse_exceptions.md
|
||||||
- features/parsing/parser_callbacks.md
|
- features/parsing/parser_callbacks.md
|
||||||
|
|||||||
+15
-15
@@ -6,7 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "check-mermaid",
|
"name": "check-mermaid",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "9.2.4",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bidi-js": "^1.1.0",
|
"bidi-js": "^1.1.0",
|
||||||
@@ -1105,9 +1105,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.4.16",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.17.tgz",
|
||||||
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
|
"integrity": "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@@ -1202,13 +1202,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "30.1.2",
|
"version": "30.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/css-color": "^7.1.3",
|
"@asamuzakjp/css-color": "^7.1.3",
|
||||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||||
"@bramus/specificity": "^2.4.2",
|
"@bramus/specificity": "^2.4.2",
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||||
"@exodus/bytes": "^1.16.0",
|
"@exodus/bytes": "^1.16.0",
|
||||||
@@ -1221,7 +1221,7 @@
|
|||||||
"parse5": "^8.0.1",
|
"parse5": "^8.0.1",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"tough-cookie": "^6.0.2",
|
"tough-cookie": "^6.0.2",
|
||||||
"undici": "^8.11.2",
|
"undici": "^8.12.1",
|
||||||
"w3c-xmlserializer": "^6.0.0",
|
"w3c-xmlserializer": "^6.0.0",
|
||||||
"webidl-conversions": "^8.0.1",
|
"webidl-conversions": "^8.0.1",
|
||||||
"whatwg-mimetype": "^5.0.0",
|
"whatwg-mimetype": "^5.0.0",
|
||||||
@@ -1521,9 +1521,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "8.11.2",
|
"version": "8.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.19.0"
|
"node": ">=22.19.0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -8,17 +8,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // count, find, min
|
#include <algorithm> // count, find, max, min
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // strlen
|
#include <cstring> // strlen
|
||||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <memory> // unique_ptr
|
||||||
#include <streambuf> // streambuf
|
#include <streambuf> // streambuf
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||||
#include <utility> // pair, declval
|
#include <utility> // declval, move, pair
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
@@ -72,6 +73,82 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
|||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#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
|
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.
|
buffer. This adapter is a very low level adapter.
|
||||||
*/
|
*/
|
||||||
@@ -209,6 +286,7 @@ class input_stream_adapter
|
|||||||
, token_segment(rhs.token_segment)
|
, token_segment(rhs.token_segment)
|
||||||
, current_in_get_area(rhs.current_in_get_area)
|
, current_in_get_area(rhs.current_in_get_area)
|
||||||
, tracking_token(rhs.tracking_token)
|
, tracking_token(rhs.tracking_token)
|
||||||
|
, no_get_area(rhs.no_get_area)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
@@ -295,37 +373,20 @@ class input_stream_adapter
|
|||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
// sbumpc() refills the get area if it is exhausted
|
// the character is in the get area: step over it, as sbumpc() does
|
||||||
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);
|
const char* const next = streambuf_access::get_ptr(sb);
|
||||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
current_in_get_area = true;
|
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();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return res;
|
return get_character_slow();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -334,6 +395,18 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
#if JSON_PRECISE_STREAM_POSITION
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
commit_lookahead();
|
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
|
#endif
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
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)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
@@ -421,7 +494,7 @@ class input_stream_adapter
|
|||||||
// last one if @a drop_last.
|
// last one if @a drop_last.
|
||||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||||
{
|
{
|
||||||
out = token_carry;
|
token_carry.copy_to(out);
|
||||||
if (token_segment != nullptr)
|
if (token_segment != nullptr)
|
||||||
{
|
{
|
||||||
out.insert(out.end(), token_segment, current_end());
|
out.insert(out.end(), token_segment, current_end());
|
||||||
@@ -434,6 +507,47 @@ class input_stream_adapter
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
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
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||||
// members through a derived class makes forming the member pointers
|
// members through a derived class makes forming the member pointers
|
||||||
@@ -457,6 +571,11 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
(s->*(&streambuf_access::gbump))(n);
|
(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
|
// one past the character last returned by get_character(), if that is in
|
||||||
@@ -477,16 +596,53 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
if (token_segment != nullptr)
|
if (token_segment != nullptr)
|
||||||
{
|
{
|
||||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
token_carry.append(token_segment, streambuf_access::get_end(sb));
|
||||||
token_segment = nullptr;
|
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
|
// character @a c was just read into a new get area (@a in_get_area) or
|
||||||
// returned without one
|
// returned without one
|
||||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||||
{
|
{
|
||||||
current_in_get_area = in_get_area;
|
current_in_get_area = in_get_area;
|
||||||
|
no_get_area = !in_get_area;
|
||||||
if (!tracking_token)
|
if (!tracking_token)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -525,13 +681,16 @@ class input_stream_adapter
|
|||||||
#endif
|
#endif
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
/// the part of the current token that was in earlier get areas
|
/// the part of the current token that was in earlier get areas
|
||||||
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
|
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||||
/// start of the current token's part in the get area, or nullptr
|
/// start of the current token's part in the get area, or nullptr
|
||||||
const char* token_segment = nullptr;
|
const char* token_segment = nullptr;
|
||||||
/// whether the character last returned by get_character() is in the get area
|
/// whether the character last returned by get_character() is in the get area
|
||||||
bool current_in_get_area = false;
|
bool current_in_get_area = false;
|
||||||
/// whether a token is tracked at all; the binary readers never start one
|
/// whether a token is tracked at all; the binary readers never start one
|
||||||
bool tracking_token = false;
|
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
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
@@ -607,17 +766,22 @@ class iterator_input_adapter
|
|||||||
return get_consumed_count() + eof_reads;
|
return get_consumed_count() + eof_reads;
|
||||||
}
|
}
|
||||||
|
|
||||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
// number of newlines in the consumed range [first_index, last_index); if
|
||||||
// is set to the index after the last of them (0 if there is none); only
|
// there is one, @a line_start is set to the index after the last of them,
|
||||||
// valid when supports_seek
|
// otherwise it is left unchanged; only valid when supports_seek
|
||||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
std::size_t count_consumed_lines(std::size_t first_index, std::size_t last_index, std::size_t& line_start) const
|
||||||
{
|
{
|
||||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
using difference_type = typename std::iterator_traits<IteratorType>::difference_type;
|
||||||
|
const auto from = std::next(begin, static_cast<difference_type>(first_index));
|
||||||
|
const auto to = std::next(from, static_cast<difference_type>(last_index - first_index));
|
||||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
const auto last_newline = std::find(reverse_it(to), reverse_it(from), newline);
|
||||||
|
if (last_newline.base() != from)
|
||||||
|
{
|
||||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
}
|
||||||
|
return static_cast<std::size_t>(std::count(from, last_newline.base(), newline));
|
||||||
}
|
}
|
||||||
|
|
||||||
// append the already-consumed characters in the half-open range
|
// append the already-consumed characters in the half-open range
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -56,8 +56,8 @@ using parser_callback_t =
|
|||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
struct sax_ignores_number_text : std::false_type {};
|
struct sax_ignores_number_text : std::false_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||||
@@ -131,7 +131,7 @@ class parser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType, true> sdp(result, allow_exceptions, &m_lexer);
|
||||||
|
|
||||||
// in case of an error, return a discarded value
|
// in case of an error, return a discarded value
|
||||||
if (!parse_dom(sdp, strict))
|
if (!parse_dom(sdp, strict))
|
||||||
@@ -153,24 +153,59 @@ class parser
|
|||||||
bool accept(const bool strict = true)
|
bool accept(const bool strict = true)
|
||||||
{
|
{
|
||||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||||
return sax_parse(&sax_acceptor, strict);
|
return sax_parse_impl<false>(&sax_acceptor, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief public SAX interface
|
||||||
|
|
||||||
|
If the SAX parser's parse_error() returns true, the parser recovers from
|
||||||
|
the error: it repairs the input and continues (see #3989).
|
||||||
|
|
||||||
|
@param[in] sax the SAX parser
|
||||||
|
@param[in] strict whether to expect the last token to be EOF
|
||||||
|
@return whether the input was parsed without errors and no SAX event
|
||||||
|
returned false
|
||||||
|
*/
|
||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse(SAX* sax, const bool strict = true)
|
bool sax_parse(SAX* sax, const bool strict = true)
|
||||||
|
{
|
||||||
|
return sax_parse_impl<true>(sax, strict);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// what sax_parse_internal() does after an object key was expected
|
||||||
|
enum class next_step : std::uint8_t
|
||||||
|
{
|
||||||
|
/// stop parsing
|
||||||
|
stop,
|
||||||
|
/// parse a value that begins with last_token
|
||||||
|
parse_value,
|
||||||
|
/// evaluate the state of the innermost container, which reads
|
||||||
|
/// last_token again
|
||||||
|
evaluate_state
|
||||||
|
};
|
||||||
|
|
||||||
|
template<bool AllowRecovery, typename SAX>
|
||||||
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
|
bool sax_parse_impl(SAX* sax, const bool strict)
|
||||||
{
|
{
|
||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
// strict mode: next byte must be EOF
|
// strict mode: next byte must be EOF; after recovering from an
|
||||||
if (get_token() != token_type::end_of_input)
|
// error, the end of the input may already have been read
|
||||||
|
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
// the value is complete, so there is nothing to recover
|
||||||
|
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||||
|
std::integral_constant<bool, AllowRecovery> {}));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -181,10 +216,9 @@ class parser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result && !error_reported;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||||
template<typename SAX>
|
template<typename SAX>
|
||||||
const string_t& number_float_text(SAX* /*sax*/)
|
const string_t& number_float_text(SAX* /*sax*/)
|
||||||
@@ -219,7 +253,7 @@ class parser
|
|||||||
template<typename DomSax>
|
template<typename DomSax>
|
||||||
bool parse_dom(DomSax& sdp, const bool strict)
|
bool parse_dom(DomSax& sdp, const bool strict)
|
||||||
{
|
{
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal<false>(&sdp);
|
||||||
|
|
||||||
if (strict)
|
if (strict)
|
||||||
{
|
{
|
||||||
@@ -239,16 +273,32 @@ class parser
|
|||||||
return !sdp.is_errored();
|
return !sdp.is_errored();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename SAX>
|
/*!
|
||||||
|
@brief parse a JSON value and pass it to a SAX parser
|
||||||
|
|
||||||
|
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
||||||
|
parse_error() returns true; false for the SAX parsers
|
||||||
|
of parse() and accept(), which never do, so that no
|
||||||
|
code for recovering is generated for them
|
||||||
|
*/
|
||||||
|
template<bool AllowRecovery, typename SAX>
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
|
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
||||||
|
// 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
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object (bytes, not std::vector<bool>, whose
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
// push_back() is a call with GCC)
|
// push_back() is a call with GCC)
|
||||||
std::vector<std::uint8_t> states;
|
std::vector<std::uint8_t> states;
|
||||||
// value to avoid a goto (see comment where set to true)
|
// a document that starts with a container pushes onto the stack at
|
||||||
bool skip_to_state_evaluation = false;
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -274,10 +324,18 @@ class parser
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse key
|
// remember we are now inside an object
|
||||||
|
states.push_back(0);
|
||||||
|
|
||||||
|
// parse key (the steps of parse_key(), which are
|
||||||
|
// repeated here and below for speed)
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
@@ -287,11 +345,12 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
|
||||||
states.push_back(0);
|
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -328,9 +387,11 @@ class parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (!overflow_error(sax, res, allow_recovery))
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||||
@@ -398,16 +459,63 @@ class parser
|
|||||||
case token_type::parse_error:
|
case token_type::parse_error:
|
||||||
{
|
{
|
||||||
// using "uninitialized" to avoid an "expected" message
|
// using "uninitialized" to avoid an "expected" message
|
||||||
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover: keep what can be read of the token
|
||||||
|
recover_token(allow_recovery);
|
||||||
|
if (last_token != token_type::uninitialized)
|
||||||
|
{
|
||||||
|
// a string or a number
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// look for the value after the garbage
|
||||||
|
if (!skip_to_value(allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// nothing could be read
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case token_type::end_of_input:
|
case token_type::end_of_input:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
// there is nothing to recover
|
||||||
|
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(),
|
||||||
|
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover: the input ends where a value is missing
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// there is no value
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!recover_missing_value(sax, states, allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
unget_token(allow_recovery);
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
case token_type::uninitialized:
|
case token_type::uninitialized:
|
||||||
case token_type::end_array:
|
case token_type::end_array:
|
||||||
@@ -417,7 +525,35 @@ class parser
|
|||||||
case token_type::literal_or_value:
|
case token_type::literal_or_value:
|
||||||
default: // the last token was unexpected
|
default: // the last token was unexpected
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (states.empty())
|
||||||
|
{
|
||||||
|
// look for the value after the garbage
|
||||||
|
if (!skip_to_value(allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (last_token == token_type::name_separator)
|
||||||
|
{
|
||||||
|
// a stray ':'; the value may follow
|
||||||
|
get_token();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!recover_missing_value(sax, states, allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
unget_token(allow_recovery);
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,7 +604,30 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (last_token == token_type::end_of_input)
|
||||||
|
{
|
||||||
|
// the input ends inside the array
|
||||||
|
return close_containers(sax, states, allow_recovery);
|
||||||
|
}
|
||||||
|
if (last_token == token_type::end_object)
|
||||||
|
{
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
states.pop_back();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
}
|
||||||
|
// otherwise, a missing ',' (or a stray ':', which value
|
||||||
|
// parsing drops): the next value begins here
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// states.back() is false -> object
|
// states.back() is false -> object
|
||||||
@@ -485,9 +644,12 @@ class parser
|
|||||||
// parse key
|
// parse key
|
||||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -496,7 +658,11 @@ class parser
|
|||||||
// parse separator (:)
|
// parse separator (:)
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
@@ -524,8 +690,544 @@ class parser
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// recover
|
||||||
|
if (last_token == token_type::end_of_input)
|
||||||
|
{
|
||||||
|
// the input ends inside the object
|
||||||
|
return close_containers(sax, states, allow_recovery);
|
||||||
|
}
|
||||||
|
if (last_token == token_type::end_array)
|
||||||
|
{
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
states.pop_back();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief continue sax_parse_internal() after a recovery
|
||||||
|
@return whether to continue parsing
|
||||||
|
*/
|
||||||
|
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
||||||
|
{
|
||||||
|
if (step == next_step::evaluate_state)
|
||||||
|
{
|
||||||
|
// the state evaluation reads the token again
|
||||||
|
m_lexer.unget_token();
|
||||||
|
skip_to_state_evaluation = true;
|
||||||
|
}
|
||||||
|
return step != next_step::stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the parser for parse() and accept() never recovers: stop parsing
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse an object key and the name separator (:) after it
|
||||||
|
|
||||||
|
last_token is the token where the key is expected. sax_parse_internal()
|
||||||
|
repeats these steps rather than calling this function, which is used
|
||||||
|
when recovering from an error.
|
||||||
|
|
||||||
|
@return next_step::parse_value if the value follows, with last_token its
|
||||||
|
first token; next_step::evaluate_state if the object's state is
|
||||||
|
to be evaluated after recovering from an error; next_step::stop
|
||||||
|
to stop parsing
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
next_step parse_key(SAX* sax)
|
||||||
|
{
|
||||||
|
const std::true_type allow_recovery{};
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||||
|
{
|
||||||
|
return key_error(sax, allow_recovery, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse separator (:)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||||
|
{
|
||||||
|
return key_error(sax, allow_recovery, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the value begins with the next token
|
||||||
|
get_token();
|
||||||
|
return next_step::parse_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a number that is too large for number_float_t, and recover
|
||||||
|
from the error by passing the value on; the SAX parser gets the
|
||||||
|
number's text as well
|
||||||
|
|
||||||
|
This is a separate function, as reading other numbers is measurably
|
||||||
|
slower if the error is handled where they are read.
|
||||||
|
|
||||||
|
@param[in] sax the SAX parser
|
||||||
|
@param[in] value the value that is not finite
|
||||||
|
@return whether to continue parsing
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
bool overflow_error(SAX* sax, const number_float_t value, std::true_type allow_recovery)
|
||||||
|
{
|
||||||
|
if (!report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sax->number_float(value, number_float_text(sax));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @copydoc overflow_error
|
||||||
|
template<typename SAX>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
std::false_type overflow_error(SAX* sax, const number_float_t /*value*/, std::false_type allow_recovery)
|
||||||
|
{
|
||||||
|
return report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a missing key, or a missing name separator (:) after the
|
||||||
|
key; the parser for parse() and accept() never recovers
|
||||||
|
|
||||||
|
@param[in] key_read whether the key was read, so that the name separator
|
||||||
|
is missing
|
||||||
|
@return std::false_type, see report_error()
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
||||||
|
{
|
||||||
|
return report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), key_read
|
||||||
|
? exception_message(token_type::name_separator, "object separator")
|
||||||
|
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report a missing key, or a missing name separator (:) after the
|
||||||
|
key, and recover from it
|
||||||
|
|
||||||
|
@param[in] key_read whether the key was read, so that the name separator
|
||||||
|
is missing
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
||||||
|
{
|
||||||
|
if (!key_read)
|
||||||
|
{
|
||||||
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
return recover_key(sax);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
||||||
|
{
|
||||||
|
return next_step::stop;
|
||||||
|
}
|
||||||
|
return recover_name_separator(sax);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////
|
||||||
|
// error recovery
|
||||||
|
/////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
The functions below repair an error after the SAX parser's parse_error()
|
||||||
|
returned true (see #3989). Each mistake is repaired by the smallest local
|
||||||
|
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
||||||
|
be read of an invalid string or number is kept (see
|
||||||
|
lexer::recover_token()), a missing value becomes null, a wrong closing
|
||||||
|
bracket closes the innermost container, and the end of the input closes
|
||||||
|
all of them. The events stay balanced, and every key() is followed by
|
||||||
|
exactly one value.
|
||||||
|
|
||||||
|
A repair hands a token to the state evaluation, by returning it to the
|
||||||
|
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
||||||
|
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
||||||
|
hands a token to value or key parsing only if it is none of them, so a
|
||||||
|
token is never handed back and forth. Every other step reads a token or
|
||||||
|
closes a container, so parsing always ends.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an error to the SAX parser; the parser for parse() and
|
||||||
|
accept() never recovers
|
||||||
|
|
||||||
|
@return std::false_type rather than false: its value is known where the
|
||||||
|
function is called even if the call is not inlined, so the code
|
||||||
|
for recovering is not generated
|
||||||
|
*/
|
||||||
|
template<typename SAX, typename Exception>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||||
|
error_reported = true;
|
||||||
|
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an error to the SAX parser
|
||||||
|
@return whether to recover from the error
|
||||||
|
*/
|
||||||
|
template<typename SAX, typename Exception>
|
||||||
|
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||||
|
const std::size_t position = m_lexer.get_position().chars_read_total;
|
||||||
|
if (error_reported && position == last_error_position && last_token == last_error_token)
|
||||||
|
{
|
||||||
|
// a repair handed on the token of the error it repaired; the
|
||||||
|
// token was reported already, and the SAX parser asked to recover
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_reported = true;
|
||||||
|
last_error_position = position;
|
||||||
|
last_error_token = last_token;
|
||||||
|
|
||||||
|
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the token string of the next error begins here
|
||||||
|
m_lexer.restart_token_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief keep what can be read of the token that the lexer rejected
|
||||||
|
|
||||||
|
The error was reported for the rejected token, so it is not reported again
|
||||||
|
for the token it is repaired to (see lexer::recover_token()).
|
||||||
|
*/
|
||||||
|
token_type recover_token()
|
||||||
|
{
|
||||||
|
last_token = m_lexer.recover_token();
|
||||||
|
last_error_position = m_lexer.get_position().chars_read_total;
|
||||||
|
last_error_token = last_token;
|
||||||
|
return last_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The functions below are called from sax_parse_internal() after an error
|
||||||
|
// was reported. The parser for parse() and accept() stopped there, so for
|
||||||
|
// it they are stubs: the code for recovering is then not even referenced,
|
||||||
|
// and unoptimized builds do not emit it either.
|
||||||
|
|
||||||
|
/// @copydoc recover_token()
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
token_type recover_token(std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
return recover_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @copydoc recover_token()
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static std::false_type recover_token(std::false_type /*allow_recovery*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return the token that was read last to the lexer (see lexer::unget_token())
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
void unget_token(std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
m_lexer.unget_token();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @copydoc unget_token(std::true_type)
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static void unget_token(std::false_type /*allow_recovery*/) noexcept {}
|
||||||
|
|
||||||
|
/// @copydoc skip_to_value
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static std::false_type skip_to_value(std::false_type /*allow_recovery*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @copydoc recover_missing_value
|
||||||
|
template<typename SAX>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static std::false_type recover_missing_value(SAX* /*sax*/, const std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pass the end events of all open containers
|
||||||
|
template<typename SAX>
|
||||||
|
bool close_containers(SAX* sax, std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
while (!states.empty())
|
||||||
|
{
|
||||||
|
const bool is_array = states.back() != 0;
|
||||||
|
states.pop_back();
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @copydoc close_containers
|
||||||
|
template<typename SAX>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
static std::false_type close_containers(SAX* /*sax*/, std::vector<std::uint8_t>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read tokens until one begins a value, skipping everything before
|
||||||
|
the top-level value
|
||||||
|
@return whether a value begins with last_token
|
||||||
|
*/
|
||||||
|
bool skip_to_value(std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (get_token())
|
||||||
|
{
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case token_type::end_of_input:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
if (last_token != token_type::uninitialized)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief skip the rest of an object member that cannot be read
|
||||||
|
|
||||||
|
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
||||||
|
not inside a container that begins in the skipped tokens, or the end of
|
||||||
|
the input.
|
||||||
|
*/
|
||||||
|
void skip_member()
|
||||||
|
{
|
||||||
|
std::size_t depth = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
++depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_object:
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
--depth;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::value_separator:
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::end_of_input:
|
||||||
|
return;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
get_token();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief pass a value where it is missing
|
||||||
|
|
||||||
|
last_token is ',', ']', '}', or the end of the input, where a value was
|
||||||
|
expected. In an object, the key gets null; in an array, a ',' where a
|
||||||
|
value is missing stands for null (as in JavaScript), while an array that
|
||||||
|
ends there just ends.
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
bool recover_missing_value(SAX* sax, const std::vector<std::uint8_t>& states, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!states.empty());
|
||||||
|
if (states.back() == 0 || last_token == token_type::value_separator)
|
||||||
|
{
|
||||||
|
return sax->null();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a missing key; last_token is where it was expected
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_key(SAX* sax)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_of_input:
|
||||||
|
// no member: the object's state handles the token
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
|
||||||
|
case token_type::parse_error:
|
||||||
|
recover_token();
|
||||||
|
if (last_token == token_type::value_string)
|
||||||
|
{
|
||||||
|
// a key that could be repaired
|
||||||
|
return parse_key(sax);
|
||||||
|
}
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
// a member without a key
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a missing name separator (:) after the key; last_token
|
||||||
|
/// is where it was expected
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_name_separator(SAX* sax)
|
||||||
|
{
|
||||||
|
switch (last_token)
|
||||||
|
{
|
||||||
|
case token_type::value_separator:
|
||||||
|
case token_type::end_object:
|
||||||
|
case token_type::end_array:
|
||||||
|
case token_type::end_of_input:
|
||||||
|
// the value is missing as well
|
||||||
|
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
||||||
|
|
||||||
|
case token_type::uninitialized:
|
||||||
|
case token_type::literal_true:
|
||||||
|
case token_type::literal_false:
|
||||||
|
case token_type::literal_null:
|
||||||
|
case token_type::value_string:
|
||||||
|
case token_type::value_unsigned:
|
||||||
|
case token_type::value_integer:
|
||||||
|
case token_type::value_float:
|
||||||
|
case token_type::begin_array:
|
||||||
|
case token_type::begin_object:
|
||||||
|
case token_type::name_separator:
|
||||||
|
case token_type::parse_error:
|
||||||
|
case token_type::literal_or_value:
|
||||||
|
default:
|
||||||
|
// a missing ':'; the value begins here
|
||||||
|
return next_step::parse_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// recover from a token after an object member that is neither ',' nor
|
||||||
|
/// '}' (nor ']' or the end of the input, which the caller handles)
|
||||||
|
template<typename SAX>
|
||||||
|
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
||||||
|
{
|
||||||
|
if (last_token == token_type::parse_error)
|
||||||
|
{
|
||||||
|
recover_token();
|
||||||
|
}
|
||||||
|
if (last_token == token_type::value_string)
|
||||||
|
{
|
||||||
|
// a missing ','; the next key begins here
|
||||||
|
return parse_key(sax);
|
||||||
|
}
|
||||||
|
skip_member();
|
||||||
|
return next_step::evaluate_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the parser for parse() and accept() never recovers (and does not come
|
||||||
|
/// here, as report_error() returned false)
|
||||||
|
template<typename SAX>
|
||||||
|
JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
||||||
|
{
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get next token from lexer
|
/// get next token from lexer
|
||||||
@@ -598,6 +1300,12 @@ class parser
|
|||||||
const bool allow_exceptions = true;
|
const bool allow_exceptions = true;
|
||||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||||
const bool ignore_trailing_commas = false;
|
const bool ignore_trailing_commas = false;
|
||||||
|
/// whether an error was reported to the SAX parser
|
||||||
|
bool error_reported = false;
|
||||||
|
/// the position of the last reported error
|
||||||
|
std::size_t last_error_position = 0;
|
||||||
|
/// the token of the last reported error
|
||||||
|
token_type last_error_token = token_type::uninitialized;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // all_of
|
#include <algorithm> // all_of, copy
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cctype> // isdigit
|
#include <cctype> // isdigit
|
||||||
#include <cerrno> // errno, ERANGE
|
#include <cerrno> // errno, ERANGE
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint64_t
|
#include <cstdint> // uint64_t
|
||||||
#include <cstdlib> // strtoull
|
#include <cstdlib> // strtoull
|
||||||
#include <cstring> // memcmp, memcpy
|
#include <cstring> // memchr, memcmp, memcpy
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
#include <iosfwd> // ostream
|
#include <iosfwd> // ostream
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
@@ -80,14 +80,38 @@ class json_pointer
|
|||||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||||
string_t to_string() const
|
string_t to_string() const
|
||||||
{
|
{
|
||||||
|
if (reference_tokens.empty())
|
||||||
|
{
|
||||||
|
return string_t();
|
||||||
|
}
|
||||||
|
|
||||||
// one buffer: every token adds its escaped form (at least its length) and a slash
|
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||||
std::size_t length = 0;
|
std::size_t length = 0;
|
||||||
|
bool plain = true;
|
||||||
for (const auto& reference_token : reference_tokens)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
length += reference_token.size() + 1;
|
length += reference_token.size() + 1;
|
||||||
|
const char* const data = reference_token.data();
|
||||||
|
for (std::size_t i = 0, size = reference_token.size(); plain && i < size; ++i)
|
||||||
|
{
|
||||||
|
plain = data[i] != '~' && data[i] != '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string_t result;
|
string_t result;
|
||||||
|
if (JSON_HEDLEY_LIKELY(plain))
|
||||||
|
{
|
||||||
|
// nothing to escape: the tokens are copied into the string
|
||||||
|
result.resize(length);
|
||||||
|
char* out = &result[0];
|
||||||
|
for (const auto& reference_token : reference_tokens)
|
||||||
|
{
|
||||||
|
*out++ = '/';
|
||||||
|
out = std::copy(reference_token.data(), reference_token.data() + reference_token.size(), out);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
result.reserve(length);
|
result.reserve(length);
|
||||||
for (const auto& reference_token : reference_tokens)
|
for (const auto& reference_token : reference_tokens)
|
||||||
{
|
{
|
||||||
@@ -303,6 +327,12 @@ class json_pointer
|
|||||||
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
exceeds_size_type ///< @a s converts to an integer that exceeds size_type
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// whether a reference token is "-" (a plain comparison with a string literal is not always inlined)
|
||||||
|
static bool is_dash(const string_t& s) noexcept
|
||||||
|
{
|
||||||
|
return s.size() == 1 && s[0] == '-';
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] s reference token to be converted into an array index
|
@param[in] s reference token to be converted into an array index
|
||||||
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
@param[out] idx the integer representation of @a s if @ref array_index_status::ok
|
||||||
@@ -328,7 +358,7 @@ class json_pointer
|
|||||||
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
// error condition (cf. RFC 6901, Sect. 4); this also covers single-
|
||||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||||
// token are left to the conversion below and are reported as unresolved
|
// token are left to the conversion below and are reported as unresolved
|
||||||
if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
|
if (JSON_HEDLEY_UNLIKELY(!s.empty() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9')))
|
||||||
{
|
{
|
||||||
return array_index_status::not_a_number;
|
return array_index_status::not_a_number;
|
||||||
}
|
}
|
||||||
@@ -475,9 +505,40 @@ class json_pointer
|
|||||||
return pos == size;
|
return pos == size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// reserve room for @a n elements of an array type that has reserve()
|
||||||
|
template<typename ArrayType>
|
||||||
|
static auto reserve_elements(ArrayType& array, const std::size_t n, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(array.reserve(n), void())
|
||||||
|
{
|
||||||
|
array.reserve(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ArrayType>
|
||||||
|
static void reserve_elements(ArrayType& /*array*/, const std::size_t /*n*/, detail::priority_tag<0> /*unused*/)
|
||||||
|
{}
|
||||||
|
|
||||||
/// @brief object member access with a key that is only copied if it is inserted
|
/// @brief object member access with a key that is only copied if it is inserted
|
||||||
|
///
|
||||||
|
/// Objects that can find the position of a key and insert there
|
||||||
|
/// (std::map) search once, others look the key up and add it if it is missing.
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
|
||||||
|
{
|
||||||
|
auto& members = *object.m_data.m_value.object;
|
||||||
|
auto it = members.lower_bound(key);
|
||||||
|
if (it != members.end() && !members.key_comp()(key, it->first))
|
||||||
|
{
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto old_capacity = object.container_capacity();
|
||||||
|
it = members.emplace_hint(it, key, nullptr);
|
||||||
|
return object.set_parent_after_insert(it->second, old_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||||
{
|
{
|
||||||
auto& members = *object.m_data.m_value.object;
|
auto& members = *object.m_data.m_value.object;
|
||||||
const auto it = members.find(key);
|
const auto it = members.find(key);
|
||||||
@@ -488,6 +549,12 @@ class json_pointer
|
|||||||
return object.operator[](key);
|
return object.operator[](key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||||
|
{
|
||||||
|
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||||
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
json_pointer top() const
|
json_pointer top() const
|
||||||
{
|
{
|
||||||
@@ -720,48 +787,103 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the values along the pointer last resolved by @ref resolve_plain
|
||||||
|
|
||||||
|
A patch from @ref basic_json::diff lists its operations in document order,
|
||||||
|
so consecutive pointers share most of their reference tokens; the values
|
||||||
|
along the previous pointer are kept to resume from the common prefix.
|
||||||
|
`steps[k]` is the value after the first `k` reference tokens together with
|
||||||
|
the length of the prefix of `*path` that holds them. A value stays valid as
|
||||||
|
long as no operation changes its container: callers keep only values up to
|
||||||
|
the parent of the pointer they modify, or call `reset()`.
|
||||||
|
*/
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
struct plain_steps
|
||||||
|
{
|
||||||
|
const string_t* path = nullptr;
|
||||||
|
std::vector<std::pair<std::size_t, BasicJsonType*>> steps{};
|
||||||
|
|
||||||
|
void reset() noexcept
|
||||||
|
{
|
||||||
|
path = nullptr;
|
||||||
|
steps.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief resolve a plain JSON pointer without building its reference tokens
|
@brief resolve a plain JSON pointer without building its reference tokens
|
||||||
|
|
||||||
@param[in] ptr the value the pointer starts at
|
@param[in] root the value the pointer starts at
|
||||||
@param[in] path a JSON pointer string
|
@param[in] path a JSON pointer string
|
||||||
@param[in] length the number of leading bytes of @a path to resolve; 0 or
|
@param[in] length the number of leading bytes of @a path to resolve; 0 or
|
||||||
the position of a slash
|
the position of a slash
|
||||||
@param[in,out] token a string to hold the reference tokens, to reuse its storage
|
@param[in,out] token a string to hold the reference tokens, to reuse its storage
|
||||||
|
@param[in,out] cache the values along the previously resolved pointer;
|
||||||
|
@a path must outlive the next call
|
||||||
|
|
||||||
@return the pointed to value, or `nullptr` if the pointer does not denote
|
@return the pointed to value, or `nullptr` if the pointer does not denote
|
||||||
an existing value, or does so in a way that is not plain: a
|
an existing value, or does so in a way that is not plain: an array
|
||||||
reference token with an escape, an array index that is malformed or
|
index that is malformed or out of range, or a "-", or a reference
|
||||||
out of range, or a "-". No exception is thrown for these; callers
|
token with an escape. No exception is
|
||||||
use @ref json_pointer instead, which handles them.
|
thrown for these; callers use @ref json_pointer instead, which
|
||||||
|
handles them.
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
|
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache)
|
||||||
{
|
{
|
||||||
for (std::size_t pos = 0; pos < length;)
|
auto& steps = cache.steps;
|
||||||
|
|
||||||
|
// resume from the deepest step that is also a prefix of this path
|
||||||
|
std::size_t depth = 1;
|
||||||
|
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
|
||||||
|
{
|
||||||
|
const string_t& previous = *cache.path;
|
||||||
|
for (depth = steps.size(); depth > 1; --depth)
|
||||||
|
{
|
||||||
|
// a step ends at a token boundary of the previous path
|
||||||
|
const std::size_t prefix = steps[depth - 1].first;
|
||||||
|
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps.resize(depth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
steps.assign(1, std::make_pair(std::size_t{0}, root));
|
||||||
|
}
|
||||||
|
cache.path = &path;
|
||||||
|
|
||||||
|
BasicJsonType* ptr = steps[depth - 1].second;
|
||||||
|
for (std::size_t pos = steps[depth - 1].first; pos < length;)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the reference token between the slash at pos and the next one
|
// the reference token between the slash at pos and the next one,
|
||||||
std::size_t end = path.find_first_of('/', pos + 1);
|
// which must not have an escape
|
||||||
if (end == string_t::npos || end > length)
|
const char* const begin = path.data() + pos + 1;
|
||||||
|
const char* const limit = path.data() + length;
|
||||||
|
const char* end = begin;
|
||||||
|
while (end < limit && *end != '/')
|
||||||
{
|
{
|
||||||
end = length;
|
if (JSON_HEDLEY_UNLIKELY(*end == '~'))
|
||||||
}
|
|
||||||
token.clear();
|
|
||||||
token.append(path.data() + pos + 1, end - pos - 1);
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(token.find_first_of('~') != string_t::npos))
|
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
++end;
|
||||||
|
}
|
||||||
|
|
||||||
switch (ptr->type())
|
switch (ptr->type())
|
||||||
{
|
{
|
||||||
case detail::value_t::object:
|
case detail::value_t::object:
|
||||||
{
|
{
|
||||||
|
token.clear();
|
||||||
|
token.append(begin, static_cast<std::size_t>(end - begin));
|
||||||
const auto it = ptr->m_data.m_value.object->find(token);
|
const auto it = ptr->m_data.m_value.object->find(token);
|
||||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||||
{
|
{
|
||||||
@@ -773,8 +895,24 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
typename BasicJsonType::size_type idx{};
|
// an index of up to 18 digits without a leading zero fits into size_type
|
||||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||||
|
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||||
|
const auto digits = static_cast<std::size_t>(end - begin);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
typename BasicJsonType::size_type idx = 0;
|
||||||
|
for (const char* c = begin; c != end; ++c)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||||
{
|
{
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -793,7 +931,8 @@ class json_pointer
|
|||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
pos = end;
|
pos = static_cast<std::size_t>(end - path.data());
|
||||||
|
steps.emplace_back(pos, ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ptr;
|
return ptr;
|
||||||
@@ -907,7 +1046,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat(
|
||||||
@@ -978,7 +1117,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" cannot be used for const access
|
// "-" cannot be used for const access
|
||||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
JSON_THROW(detail::out_of_range::create(detail::exception_id::pointer_past_the_end_index, detail::concat("array index '-' (", std::to_string(ptr->m_data.m_value.array->size()), ") is out of range"), ptr));
|
||||||
@@ -1040,7 +1179,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -1117,7 +1256,7 @@ class json_pointer
|
|||||||
|
|
||||||
case detail::value_t::array:
|
case detail::value_t::array:
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||||
{
|
{
|
||||||
// "-" always fails the range check
|
// "-" always fails the range check
|
||||||
return false;
|
return false;
|
||||||
@@ -1198,33 +1337,32 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
result.reserve(token_count);
|
result.reserve(token_count);
|
||||||
|
|
||||||
// extract the reference tokens:
|
// extract the reference tokens: every one runs from after a slash to
|
||||||
// - slash: position of the last read slash (or end of string)
|
// the next slash or the end of the string; a token without a '~' (the
|
||||||
// - start: position after the previous slash
|
// common case) is stored as it is, others are checked and unescaped
|
||||||
for (
|
const char* const data = reference_string.data();
|
||||||
// search for the first slash after the first character
|
const char* const end = data + size;
|
||||||
std::size_t slash = reference_string.find_first_of('/', 1),
|
for (const char* start = data + 1;; ++start)
|
||||||
// set the beginning of the first reference token
|
|
||||||
start = 1;
|
|
||||||
// we can stop if start == 0 (if slash == string_t::npos)
|
|
||||||
start != 0;
|
|
||||||
// set the beginning of the next reference token
|
|
||||||
// (will eventually be 0 if slash == string_t::npos)
|
|
||||||
start = (slash == string_t::npos) ? 0 : slash + 1,
|
|
||||||
// find next slash
|
|
||||||
slash = reference_string.find_first_of('/', start))
|
|
||||||
{
|
{
|
||||||
// use the text between the beginning of the reference token
|
const char* slash = start;
|
||||||
// (start) and the last slash (slash).
|
bool escaped = false;
|
||||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
while (slash != end && *slash != '/')
|
||||||
auto reference_token = string_t(reference_string.data() + start, count);
|
{
|
||||||
|
escaped = escaped || *slash == '~';
|
||||||
|
++slash;
|
||||||
|
}
|
||||||
|
|
||||||
// check reference tokens are properly escaped; a token without
|
const auto count = static_cast<std::size_t>(slash - start);
|
||||||
// a '~' (the common case) needs neither check nor unescaping
|
if (JSON_HEDLEY_LIKELY(!escaped))
|
||||||
std::size_t pos = reference_token.find_first_of('~');
|
|
||||||
if (pos != string_t::npos)
|
|
||||||
{
|
{
|
||||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
result.emplace_back(start, count);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto reference_token = string_t(start, count);
|
||||||
|
|
||||||
|
// check reference tokens are properly escaped
|
||||||
|
for (std::size_t pos = reference_token.find_first_of('~'); pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||||
{
|
{
|
||||||
JSON_ASSERT(reference_token[pos] == '~');
|
JSON_ASSERT(reference_token[pos] == '~');
|
||||||
|
|
||||||
@@ -1238,10 +1376,14 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
|
|
||||||
detail::unescape(reference_token);
|
detail::unescape(reference_token);
|
||||||
|
result.push_back(std::move(reference_token));
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally, store the reference token
|
if (slash == end)
|
||||||
result.push_back(std::move(reference_token));
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start = slash;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -1276,6 +1418,52 @@ class json_pointer
|
|||||||
obj[key] = std::forward<ValueType>(v);
|
obj[key] = std::forward<ValueType>(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the number of entries that flattening @a value adds: its primitive
|
||||||
|
/// values and empty containers
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
static std::size_t count_flattened(const BasicJsonType& value)
|
||||||
|
{
|
||||||
|
std::size_t count = 0;
|
||||||
|
std::vector<const BasicJsonType*> pending(1, &value);
|
||||||
|
while (!pending.empty())
|
||||||
|
{
|
||||||
|
const BasicJsonType* const v = pending.back();
|
||||||
|
pending.pop_back();
|
||||||
|
if (v->is_array() && !v->m_data.m_value.array->empty())
|
||||||
|
{
|
||||||
|
for (const auto& element : *v->m_data.m_value.array)
|
||||||
|
{
|
||||||
|
pending.push_back(&element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (v->is_object() && !v->m_data.m_value.object->empty())
|
||||||
|
{
|
||||||
|
for (const auto& member : *v->m_data.m_value.object)
|
||||||
|
{
|
||||||
|
pending.push_back(&member.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserve the storage for @a value of objects that have a reserve(), so
|
||||||
|
// that it does not have to be moved while it grows
|
||||||
|
template<typename ObjectType, typename BasicJsonType>
|
||||||
|
static auto reserve_flattened(ObjectType& obj, const BasicJsonType& value, detail::priority_tag<1> /*unused*/)
|
||||||
|
-> decltype(obj.reserve(std::size_t {}), void())
|
||||||
|
{
|
||||||
|
obj.reserve(count_flattened(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ObjectType, typename BasicJsonType>
|
||||||
|
static void reserve_flattened(ObjectType& /*obj*/, const BasicJsonType& /*value*/, detail::priority_tag<0> /*unused*/)
|
||||||
|
{}
|
||||||
|
|
||||||
/// append the decimal representation of @a i to @a out
|
/// append the decimal representation of @a i to @a out
|
||||||
static void append_index(string_t& out, std::size_t i)
|
static void append_index(string_t& out, std::size_t i)
|
||||||
{
|
{
|
||||||
@@ -1385,6 +1573,7 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
reserve_flattened(object, value, detail::priority_tag<1> {});
|
||||||
enter(value);
|
enter(value);
|
||||||
while (!stack.empty())
|
while (!stack.empty())
|
||||||
{
|
{
|
||||||
@@ -1392,8 +1581,9 @@ class json_pointer
|
|||||||
// frame, which would invalidate a reference to it
|
// frame, which would invalidate a reference to it
|
||||||
const BasicJsonType* const container = stack.back().container;
|
const BasicJsonType* const container = stack.back().container;
|
||||||
|
|
||||||
// drop the path of the previous child
|
// the path of the previous child is replaced by the path of the
|
||||||
path.resize(stack.back().path_length);
|
// next one; for an array element in one resize
|
||||||
|
const std::size_t path_length = stack.back().path_length;
|
||||||
|
|
||||||
if (container->is_array())
|
if (container->is_array())
|
||||||
{
|
{
|
||||||
@@ -1407,8 +1597,20 @@ class json_pointer
|
|||||||
|
|
||||||
// iterate array and use index as a reference string
|
// iterate array and use index as a reference string
|
||||||
++stack.back().index;
|
++stack.back().index;
|
||||||
path.push_back('/');
|
std::array<char, 24> digits{};
|
||||||
append_index(path, i);
|
std::size_t pos = digits.size();
|
||||||
|
std::size_t rest = i;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
digits[--pos] = static_cast<char>('0' + (rest % 10));
|
||||||
|
rest /= 10;
|
||||||
|
}
|
||||||
|
while (rest != 0);
|
||||||
|
const std::size_t count = digits.size() - pos;
|
||||||
|
path.resize(path_length + 1 + count);
|
||||||
|
char* const out = &path[path_length];
|
||||||
|
out[0] = '/';
|
||||||
|
std::memcpy(out + 1, digits.data() + pos, count);
|
||||||
enter(array[i]);
|
enter(array[i]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1422,6 +1624,7 @@ class json_pointer
|
|||||||
|
|
||||||
// iterate object and use keys as reference string
|
// iterate object and use keys as reference string
|
||||||
++stack.back().member;
|
++stack.back().member;
|
||||||
|
path.resize(path_length);
|
||||||
path.push_back('/');
|
path.push_back('/');
|
||||||
detail::escape_into(path, it->first);
|
detail::escape_into(path, it->first);
|
||||||
enter(it->second);
|
enter(it->second);
|
||||||
@@ -1517,8 +1720,28 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
end = key.size();
|
end = key.size();
|
||||||
}
|
}
|
||||||
|
const std::size_t token_size = end - pos - 1;
|
||||||
|
|
||||||
|
// a token that is a plain index of up to 9 digits, which is
|
||||||
|
// the case for almost all array elements, is not copied
|
||||||
|
std::size_t index = 0;
|
||||||
|
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
|
||||||
|
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
|
||||||
|
{
|
||||||
|
plain_index = data[k] >= '0' && data[k] <= '9';
|
||||||
|
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
|
||||||
|
}
|
||||||
|
if (plain_index)
|
||||||
|
{
|
||||||
|
current = ¤t->operator[](static_cast<typename BasicJsonType::size_type>(index));
|
||||||
|
values.push_back(current);
|
||||||
|
ends.push_back(end);
|
||||||
|
pos = end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
token.clear();
|
token.clear();
|
||||||
token.append(data + pos + 1, end - pos - 1);
|
token.append(data + pos + 1, token_size);
|
||||||
if (token.find_first_of('~') != string_t::npos)
|
if (token.find_first_of('~') != string_t::npos)
|
||||||
{
|
{
|
||||||
detail::unescape(token);
|
detail::unescape(token);
|
||||||
@@ -1528,10 +1751,13 @@ class json_pointer
|
|||||||
{
|
{
|
||||||
case detail::value_t::null:
|
case detail::value_t::null:
|
||||||
{
|
{
|
||||||
if (arrays.contains(data, pos))
|
// a first child 0 makes the value an array without a look-up
|
||||||
|
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||||
{
|
{
|
||||||
// some reference token below this position is 0, so the
|
// some reference token below this position is 0, so the
|
||||||
// value is an array
|
// value is an array
|
||||||
|
*current = BasicJsonType(detail::value_t::array);
|
||||||
|
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
|
||||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -215,6 +215,17 @@
|
|||||||
#define JSON_NO_UNIQUE_ADDRESS
|
#define JSON_NO_UNIQUE_ADDRESS
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Inlines small functions even in unoptimized builds, so that they are not
|
||||||
|
// emitted. The parsers for parse(), accept(), and from_*() use it for the
|
||||||
|
// functions that stand in for the code recovering from errors (see #3989).
|
||||||
|
// MSVC is left to decide, as it warns (C4714) where it does not inline a
|
||||||
|
// __forceinline function.
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__)
|
||||||
|
#define JSON_INTERNAL_ALWAYS_INLINE
|
||||||
|
#else
|
||||||
|
#define JSON_INTERNAL_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||||
// constructor uses to bound its descent: every test that copies a value
|
// constructor uses to bound its descent: every test that copies a value
|
||||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||||
#undef JSON_INLINE_VARIABLE
|
#undef JSON_INLINE_VARIABLE
|
||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
|
#undef JSON_INTERNAL_ALWAYS_INLINE
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,66 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
|||||||
/// output_vector_sink also appends writes of at least this size with insert()
|
/// output_vector_sink also appends writes of at least this size with insert()
|
||||||
constexpr std::size_t max_reserve_size = 256;
|
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
|
/// @brief non-virtual output sink writing into a std::vector
|
||||||
///
|
///
|
||||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||||
: v(&vec), position(vec.size())
|
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
output_vector_sink(const output_vector_sink&) = delete;
|
output_vector_sink(const output_vector_sink&) = delete;
|
||||||
@@ -86,7 +146,7 @@ class output_vector_sink
|
|||||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||||
|
|
||||||
output_vector_sink(output_vector_sink&& other) noexcept
|
output_vector_sink(output_vector_sink&& other) noexcept
|
||||||
: v(other.v), position(other.position)
|
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||||
{
|
{
|
||||||
other.v = nullptr;
|
other.v = nullptr;
|
||||||
}
|
}
|
||||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
if (v != nullptr)
|
if (v != nullptr)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
v->resize(position());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||||
{
|
{
|
||||||
grow(1);
|
grow(1);
|
||||||
}
|
}
|
||||||
(*v)[position++] = c;
|
*cursor++ = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
if (length >= max_reserve_size)
|
if (length >= max_reserve_size)
|
||||||
{
|
{
|
||||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
|||||||
}
|
}
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
if (length != 0)
|
if (length <= 32)
|
||||||
{
|
{
|
||||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
position += length;
|
cursor += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||||
|
cursor += length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,21 +209,37 @@ class output_vector_sink
|
|||||||
CharType* reserve(const std::size_t length)
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
return v->data() + position;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// keep the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void commit(const std::size_t length) noexcept
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
position += length;
|
cursor += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||||
|
void flush() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
private:
|
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;
|
/// 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
|
/// kept out of line so that the write functions stay small enough for
|
||||||
/// GCC to inline them into the writer
|
/// GCC to inline them into the writer
|
||||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
|||||||
void grow(const std::size_t length)
|
void grow(const std::size_t length)
|
||||||
{
|
{
|
||||||
// use the capacity the vector already has, else at least double it;
|
// use the capacity the vector already has, else at least double it;
|
||||||
// never less than 64 characters, so that a small value does not
|
// never less than min_vector_sink_capacity, so that a small value does not
|
||||||
// reallocate for every few bytes
|
// reallocate for every few bytes
|
||||||
const std::size_t needed = position + length;
|
const std::size_t written = position();
|
||||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
const std::size_t needed = written + length;
|
||||||
|
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||||
? v->capacity()
|
? v->capacity()
|
||||||
: (std::max)(needed, v->capacity() * 2)));
|
: (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
|
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||||
@@ -181,20 +265,24 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
void append(const CharType* s, const std::size_t length)
|
void append(const CharType* s, const std::size_t length)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
const std::size_t written = position();
|
||||||
if (v->capacity() - position < length)
|
v->resize(written);
|
||||||
|
if (v->capacity() - written < length)
|
||||||
{
|
{
|
||||||
// grow geometrically, with room for the writes that follow, so
|
// grow geometrically, with room for the writes that follow, so
|
||||||
// that the next grow() need not reallocate right away
|
// that the next grow() need not reallocate right away
|
||||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||||
}
|
}
|
||||||
v->insert(v->end(), s, s + length);
|
v->insert(v->end(), s, s + length);
|
||||||
position = v->size();
|
cursor = v->data() + v->size();
|
||||||
|
limit = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CharType, AllocatorType>* v;
|
std::vector<CharType, AllocatorType>* v;
|
||||||
/// number of characters of *v written so far
|
/// the write position: in *v, at the number of characters written so far
|
||||||
std::size_t position;
|
CharType* cursor;
|
||||||
|
/// the end of *v
|
||||||
|
CharType* limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// output adapter for byte vectors
|
/// output adapter for byte vectors
|
||||||
@@ -277,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
|||||||
///
|
///
|
||||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
/// adapters) via the `output_adapter`-based overloads.
|
||||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
///
|
||||||
/// avoid it.
|
/// 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.
|
||||||
template<typename CharType>
|
template<typename CharType>
|
||||||
class output_adapter_sink
|
class output_adapter_sink
|
||||||
{
|
{
|
||||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
|||||||
JSON_ASSERT(oa);
|
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)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
oa->write_character(c);
|
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
buffer[used++] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||||
// the type-erased path already did before this sink existed
|
// the type-erased path already did before this sink existed
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
oa->write_characters(s, 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// space for writing up to @a length characters (see
|
/// space for writing up to @a length characters (see
|
||||||
/// output_vector_sink::reserve)
|
/// output_vector_sink::reserve)
|
||||||
CharType* reserve(const std::size_t length) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
static_cast<void>(length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
return reserved.data();
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
return buffer.data() + used;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// write the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
void commit(const std::size_t length)
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
oa->write_characters(reserved.data(), 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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;
|
output_adapter_t<CharType> oa;
|
||||||
std::array<CharType, max_reserve_size> reserved{};
|
/// number of characters of @ref buffer not yet passed on
|
||||||
|
std::size_t used = 0;
|
||||||
|
std::array<CharType, buffer_size> buffer{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class serializer
|
|||||||
|
|
||||||
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
None of @a pretty_print_, @a ensure_ascii_ and @a indent_step_ change over
|
||||||
the life of the serializer, so they are captured once here instead of
|
the life of the serializer, so they are captured once here instead of
|
||||||
being threaded through every call to @ref dump, @ref dump_internal and
|
being threaded through every call to @ref dump, @ref dump_container and
|
||||||
@ref dump_iteratively.
|
@ref dump_iteratively.
|
||||||
*/
|
*/
|
||||||
serializer(output_adapter_protocol<char>& s, const char ichar,
|
serializer(output_adapter_protocol<char>& s, const char ichar,
|
||||||
@@ -80,11 +80,14 @@ class serializer
|
|||||||
const std::size_t indent_step_ = 0,
|
const std::size_t indent_step_ = 0,
|
||||||
error_handler_t error_handler_ = error_handler_t::strict)
|
error_handler_t error_handler_ = error_handler_t::strict)
|
||||||
: o(&s)
|
: o(&s)
|
||||||
|
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
|
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
, indent_char(ichar)
|
, indent_char(ichar)
|
||||||
, pretty_print(pretty_print_)
|
, pretty_print(pretty_print_)
|
||||||
, ensure_ascii(ensure_ascii_)
|
, ensure_ascii(ensure_ascii_)
|
||||||
, indent_step(indent_step_)
|
, indent_step(indent_step_)
|
||||||
, error_handler(error_handler_)
|
, error_handler(error_handler_)
|
||||||
|
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -115,18 +118,18 @@ class serializer
|
|||||||
void dump(const BasicJsonType& val,
|
void dump(const BasicJsonType& val,
|
||||||
const std::size_t current_indent = 0)
|
const std::size_t current_indent = 0)
|
||||||
{
|
{
|
||||||
dump_internal(val, current_indent);
|
dump_element(val, current_indent, 0);
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief worker for @ref dump
|
@brief serialize the object or array @a val
|
||||||
|
|
||||||
Identical in behavior to the historical @ref dump, but writes into the
|
Writes into the serializer's internal @ref write_buffer instead of issuing
|
||||||
serializer's internal @ref write_buffer instead of issuing a virtual call
|
a virtual call per token; the public @ref dump flushes the buffer once the
|
||||||
per token. The public @ref dump wraps this and flushes the buffer once the
|
top-level value has been serialized. Scalars are written by @ref dump_scalar,
|
||||||
top-level value has been serialized.
|
which @ref dump_element calls without entering this function.
|
||||||
|
|
||||||
Serializing a container descends into its elements, so a value nested deeply
|
Serializing a container descends into its elements, so a value nested deeply
|
||||||
enough used to exhaust the call stack and terminate the process with no
|
enough used to exhaust the call stack and terminate the process with no
|
||||||
@@ -137,20 +140,20 @@ class serializer
|
|||||||
|
|
||||||
@sa https://github.com/nlohmann/json/issues/5387
|
@sa https://github.com/nlohmann/json/issues/5387
|
||||||
*/
|
*/
|
||||||
void dump_internal(const BasicJsonType& val,
|
void dump_container(const BasicJsonType& val,
|
||||||
const std::size_t current_indent = 0,
|
const std::size_t current_indent,
|
||||||
const std::size_t depth = 0)
|
const std::size_t depth)
|
||||||
{
|
|
||||||
switch (val.m_data.m_type)
|
|
||||||
{
|
|
||||||
case value_t::object:
|
|
||||||
{
|
{
|
||||||
|
JSON_ASSERT(val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array);
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||||
{
|
{
|
||||||
dump_iteratively(val, current_indent);
|
dump_iteratively(val, current_indent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (val.m_data.m_type == value_t::object)
|
||||||
|
{
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
put_literal("{}");
|
put_literal("{}");
|
||||||
@@ -159,34 +162,27 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("{\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("{\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
put_object_key(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_indent(new_indent);
|
put_object_key(i->first, "\": ");
|
||||||
put_char('"');
|
dump_element(i->second, new_indent, depth + 1);
|
||||||
dump_escaped(i->first);
|
|
||||||
put_literal("\": ");
|
|
||||||
dump_internal(i->second, new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -197,32 +193,19 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
put_char('"');
|
put_object_key(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, 0, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
put_char('"');
|
put_object_key(i->first, "\":");
|
||||||
dump_escaped(i->first);
|
dump_element(i->second, 0, depth + 1);
|
||||||
put_literal("\":");
|
|
||||||
dump_internal(i->second, current_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
|
||||||
{
|
|
||||||
dump_iteratively(val, current_indent);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,27 +217,24 @@ class serializer
|
|||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("[\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("[\n", new_indent);
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
put_indent(new_indent);
|
dump_element(*i, new_indent, depth + 1);
|
||||||
dump_internal(*i, new_indent, depth + 1);
|
put_literal_indent(",\n", new_indent);
|
||||||
put_literal(",\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
put_indent(new_indent);
|
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
||||||
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
|
|
||||||
|
|
||||||
put_char('\n');
|
put_literal_indent("\n", current_indent);
|
||||||
put_indent(current_indent);
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -265,39 +245,44 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_internal(*i, current_indent, depth + 1);
|
dump_element(*i, 0, depth + 1);
|
||||||
put_char(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
dump_internal(val.m_data.m_value.array->back(), current_indent, depth + 1);
|
dump_element(val.m_data.m_value.array->back(), 0, depth + 1);
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::discarded:
|
|
||||||
case value_t::null:
|
|
||||||
default:
|
|
||||||
dump_scalar(val, current_indent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/*!
|
||||||
|
@brief serialize an element of a container
|
||||||
|
|
||||||
|
Most elements are scalars, which need none of the state dump_container() keeps
|
||||||
|
for a container; writing them from here spares each the entry and exit of
|
||||||
|
that function.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE void dump_element(const BasicJsonType& val,
|
||||||
|
const std::size_t current_indent,
|
||||||
|
const std::size_t depth)
|
||||||
|
{
|
||||||
|
if (val.m_data.m_type == value_t::object || val.m_data.m_type == value_t::array)
|
||||||
|
{
|
||||||
|
dump_container(val, current_indent, depth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_scalar(val, current_indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief write out @a val and everything below it without the call stack
|
@brief write out @a val and everything below it without the call stack
|
||||||
|
|
||||||
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
Emits the same bytes as @ref dump_container, keeping the containers it has
|
||||||
entered on an explicit stack instead of descending into them. Only reached
|
entered on an explicit stack instead of descending into them. Only reached
|
||||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||||
written for speed: walking every value this way measured up to 20% slower on
|
written for speed: walking every value this way measured up to 20% slower on
|
||||||
@@ -325,8 +310,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_char('\n');
|
put_literal_indent("\n", frame.current_indent);
|
||||||
put_indent(frame.current_indent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('}');
|
put_char('}');
|
||||||
@@ -353,16 +337,13 @@ class serializer
|
|||||||
put_indent(frame.child_indent);
|
put_indent(frame.child_indent);
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char('"');
|
|
||||||
dump_escaped(frame.object_it->first);
|
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_literal("\": ");
|
put_object_key(frame.object_it->first, "\": ");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
put_literal("\":");
|
put_object_key(frame.object_it->first, "\":");
|
||||||
}
|
}
|
||||||
|
|
||||||
const BasicJsonType& element = frame.object_it->second;
|
const BasicJsonType& element = frame.object_it->second;
|
||||||
@@ -381,8 +362,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
put_char('\n');
|
put_literal_indent("\n", frame.current_indent);
|
||||||
put_indent(frame.current_indent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
put_char(']');
|
put_char(']');
|
||||||
@@ -523,7 +503,7 @@ class serializer
|
|||||||
/*!
|
/*!
|
||||||
@brief serialize the value @a val, which is neither an object nor an array
|
@brief serialize the value @a val, which is neither an object nor an array
|
||||||
|
|
||||||
Shared by @ref dump_internal and @ref dump_value, so that a value is written
|
Shared by @ref dump_element and @ref dump_value, so that a value is written
|
||||||
the same way however deeply it is nested. A binary value is written out here
|
the same way however deeply it is nested. A binary value is written out here
|
||||||
in full: it looks like an object, but has no elements to descend into.
|
in full: it looks like an object, but has no elements to descend into.
|
||||||
|
|
||||||
@@ -537,78 +517,13 @@ class serializer
|
|||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
put_char('"');
|
dump_string_value(*val.m_data.m_value.string);
|
||||||
dump_escaped(*val.m_data.m_value.string);
|
|
||||||
put_char('"');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
{
|
{
|
||||||
if (pretty_print)
|
dump_binary_value(current_indent, *val.m_data.m_value.binary);
|
||||||
{
|
|
||||||
put_literal("{\n");
|
|
||||||
|
|
||||||
// variable to hold indentation for the bytes
|
|
||||||
const auto new_indent = next_indent(current_indent, indent_step);
|
|
||||||
|
|
||||||
put_indent(new_indent);
|
|
||||||
|
|
||||||
put_literal("\"bytes\": [");
|
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
|
||||||
{
|
|
||||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
||||||
{
|
|
||||||
dump_byte(*i);
|
|
||||||
put_literal(", ");
|
|
||||||
}
|
|
||||||
dump_byte(val.m_data.m_value.binary->back());
|
|
||||||
}
|
|
||||||
|
|
||||||
put_literal("],\n");
|
|
||||||
put_indent(new_indent);
|
|
||||||
|
|
||||||
put_literal("\"subtype\": ");
|
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
|
||||||
{
|
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
put_literal("null");
|
|
||||||
}
|
|
||||||
put_char('\n');
|
|
||||||
put_indent(current_indent);
|
|
||||||
put_char('}');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
put_literal("{\"bytes\":[");
|
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
|
||||||
{
|
|
||||||
for (auto i = val.m_data.m_value.binary->cbegin();
|
|
||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
|
||||||
{
|
|
||||||
dump_byte(*i);
|
|
||||||
put_char(',');
|
|
||||||
}
|
|
||||||
dump_byte(val.m_data.m_value.binary->back());
|
|
||||||
}
|
|
||||||
|
|
||||||
put_literal("],\"subtype\":");
|
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
|
||||||
{
|
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
|
||||||
put_char('}');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
put_literal("null}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -627,19 +542,19 @@ class serializer
|
|||||||
|
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.number_integer);
|
dump_signed_value(val.m_data.m_value.number_integer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.number_unsigned);
|
dump_unsigned_value(val.m_data.m_value.number_unsigned);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
{
|
{
|
||||||
dump_float(val.m_data.m_value.number_float);
|
dump_float_value(val.m_data.m_value.number_float);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,6 +577,100 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief write the string @a s in quotes; kept out of line, so that the
|
||||||
|
/// other scalars do not pay for its register needs (and the other way round)
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void dump_string_value(const string_t& s)
|
||||||
|
{
|
||||||
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
if (s.size() <= short_string_max && put_short_quoted(s, "\""))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
put_char('"');
|
||||||
|
dump_escaped(s);
|
||||||
|
put_char('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The numbers, and the rest of what dump_scalar() writes, are left to
|
||||||
|
// functions of their own as well: a single function holding all of them
|
||||||
|
// saves and restores every register any of them needs, on every call. Now
|
||||||
|
// dump_scalar() only dispatches, and each writer pays for what it uses.
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void dump_signed_value(const number_integer_t x)
|
||||||
|
{
|
||||||
|
dump_integer(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void dump_unsigned_value(const number_unsigned_t x)
|
||||||
|
{
|
||||||
|
dump_integer(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void dump_float_value(const number_float_t x)
|
||||||
|
{
|
||||||
|
dump_float(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void dump_binary_value(const std::size_t current_indent, const typename BasicJsonType::binary_t& binary)
|
||||||
|
{
|
||||||
|
if (pretty_print)
|
||||||
|
{
|
||||||
|
// variable to hold indentation for the bytes
|
||||||
|
const auto new_indent = next_indent(current_indent, indent_step);
|
||||||
|
|
||||||
|
put_literal_indent("{\n", new_indent);
|
||||||
|
put_literal("\"bytes\": [");
|
||||||
|
|
||||||
|
if (!binary.empty())
|
||||||
|
{
|
||||||
|
for (auto i = binary.cbegin(); i != binary.cend() - 1; ++i)
|
||||||
|
{
|
||||||
|
dump_byte(*i);
|
||||||
|
put_literal(", ");
|
||||||
|
}
|
||||||
|
dump_byte(binary.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
put_literal_indent("],\n", new_indent);
|
||||||
|
|
||||||
|
put_literal("\"subtype\": ");
|
||||||
|
if (binary.has_subtype())
|
||||||
|
{
|
||||||
|
dump_integer(binary.subtype());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put_literal("null");
|
||||||
|
}
|
||||||
|
put_literal_indent("\n", current_indent);
|
||||||
|
put_char('}');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put_literal("{\"bytes\":[");
|
||||||
|
|
||||||
|
if (!binary.empty())
|
||||||
|
{
|
||||||
|
for (auto i = binary.cbegin(); i != binary.cend() - 1; ++i)
|
||||||
|
{
|
||||||
|
dump_byte(*i);
|
||||||
|
put_char(',');
|
||||||
|
}
|
||||||
|
dump_byte(binary.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
put_literal("],\"subtype\":");
|
||||||
|
if (binary.has_subtype())
|
||||||
|
{
|
||||||
|
dump_integer(binary.subtype());
|
||||||
|
put_char('}');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
put_literal("null}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the indentation level to use for the children of the current value
|
@brief the indentation level to use for the children of the current value
|
||||||
@@ -678,6 +687,220 @@ class serializer
|
|||||||
return new_indent;
|
return new_indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
@brief append the object key @a s in quotes, followed by @a tail
|
||||||
|
|
||||||
|
@a tail is the closing quote and the colon, followed by a space when
|
||||||
|
pretty-printing. The callers pass it because each of them already sits on
|
||||||
|
the pretty-printing or the compact path; choosing it here would test
|
||||||
|
@ref pretty_print once more for every key. Most keys are short and plain
|
||||||
|
and are written in one go by put_short_quoted(); everything else takes the
|
||||||
|
general path.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
void put_object_key(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
{
|
||||||
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
if (put_short_quoted(s, tail))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
put_char('"');
|
||||||
|
dump_escaped(s);
|
||||||
|
put_literal(tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// put_short_quoted() assembles words from little-endian loads
|
||||||
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
/// the longest string put_short_quoted() takes
|
||||||
|
static constexpr std::size_t short_string_max = JSON_SCAN_VECTOR ? 32 : 16;
|
||||||
|
|
||||||
|
/// the high bit of every byte of @a v that is not printable ASCII or is a
|
||||||
|
/// quote, a backslash, or DEL (zero if the bytes can be copied as they are)
|
||||||
|
static std::uint64_t swar_not_plain(const std::uint64_t v) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"'
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\'
|
||||||
|
return (((q - ones) & ~q) | ((b - ones) & ~b) // == '"', == '\\'
|
||||||
|
| ((v - 0x2020202020202020ull) & ~v) // < 0x20
|
||||||
|
| v | (v + ones)) // >= 0x7F
|
||||||
|
& 0x8080808080808080ull;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename WordType>
|
||||||
|
static WordType load_word(const char* p) noexcept
|
||||||
|
{
|
||||||
|
WordType w;
|
||||||
|
std::memcpy(&w, p, sizeof(w));
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename WordType>
|
||||||
|
static void store_word(char* p, const WordType w) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(p, &w, sizeof(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write `"`, @a s and @a tail if @a s is short and needs no escaping
|
||||||
|
|
||||||
|
A string of up to 16 (32 with vector instructions) printable ASCII
|
||||||
|
characters (no quote, backslash, or DEL) is read with two overlapping loads
|
||||||
|
and written with two overlapping stores, instead of scanning it and calling
|
||||||
|
memcpy with a length that is only known at run time. Returns false, having
|
||||||
|
changed nothing, if @a s does not qualify or the write buffer lacks the
|
||||||
|
room.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
bool put_short_quoted(const string_t& s, const char (&tail)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
{
|
||||||
|
const std::size_t n = s.size();
|
||||||
|
if (n > short_string_max || write_buffer_pos + n + N > write_buffer.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* d = write_buffer.data() + write_buffer_pos + 1;
|
||||||
|
const char* const p = s.data();
|
||||||
|
|
||||||
|
if (n >= 8)
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
if (!(n >= 16 ? copy_plain_vectors(d, p, n) : copy_plain_halves(d, p, n)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const auto a = load_word<std::uint64_t>(p);
|
||||||
|
const auto b = load_word<std::uint64_t>(p + n - 8);
|
||||||
|
if ((swar_not_plain(a) | swar_not_plain(b)) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
store_word(d, a);
|
||||||
|
store_word(d + n - 8, b);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (n >= 4)
|
||||||
|
{
|
||||||
|
const auto a = load_word<std::uint32_t>(p);
|
||||||
|
const auto b = load_word<std::uint32_t>(p + n - 4);
|
||||||
|
// the bytes above the string are filled with a plain character
|
||||||
|
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 4)))
|
||||||
|
| (0x4141414141414141ull << (8 * n));
|
||||||
|
if (swar_not_plain(w) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
store_word(d, a);
|
||||||
|
store_word(d + n - 4, b);
|
||||||
|
}
|
||||||
|
else if (n >= 2)
|
||||||
|
{
|
||||||
|
const auto a = load_word<std::uint16_t>(p);
|
||||||
|
const auto b = load_word<std::uint16_t>(p + n - 2);
|
||||||
|
const std::uint64_t w = a | (static_cast<std::uint64_t>(b) << (8 * (n - 2)))
|
||||||
|
| (0x4141414141414141ull << (8 * n));
|
||||||
|
if (swar_not_plain(w) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
store_word(d, a);
|
||||||
|
store_word(d + n - 2, b);
|
||||||
|
}
|
||||||
|
else if (n == 1)
|
||||||
|
{
|
||||||
|
const std::uint64_t w = static_cast<std::uint64_t>(static_cast<unsigned char>(p[0])) | (0x4141414141414141ull << 8u);
|
||||||
|
if (swar_not_plain(w) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*d = p[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
d[-1] = '"';
|
||||||
|
std::memcpy(d + n, tail, N - 1);
|
||||||
|
write_buffer_pos += n + N;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/// copy the 8 to 15 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||||
|
/// words, tested together as one vector, see copy_plain_vectors()
|
||||||
|
static bool copy_plain_halves(char* d, const char* p, const std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x8_t lo = vld1_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||||
|
const uint8x8_t hi = vld1_u8(reinterpret_cast<const std::uint8_t*>(p + n - 8));
|
||||||
|
const uint8x16_t in = vcombine_u8(lo, hi);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||||
|
vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)), vceqq_u8(in, vdupq_n_u8(0x7F))));
|
||||||
|
if (vmaxvq_u8(special) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vst1_u8(reinterpret_cast<std::uint8_t*>(d), lo);
|
||||||
|
vst1_u8(reinterpret_cast<std::uint8_t*>(d + n - 8), hi);
|
||||||
|
#else
|
||||||
|
const __m128i lo = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i hi = _mm_loadl_epi64(static_cast<const __m128i*>(static_cast<const void*>(p + n - 8))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i in = _mm_unpacklo_epi64(lo, hi);
|
||||||
|
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_cmplt_epi8(in, _mm_set1_epi8(0x20)), _mm_cmpeq_epi8(in, _mm_set1_epi8(0x7F))));
|
||||||
|
if (_mm_movemask_epi8(special) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d)), lo); // NOLINT(bugprone-casting-through-void)
|
||||||
|
_mm_storel_epi64(static_cast<__m128i*>(static_cast<void*>(d + n - 8)), hi); // NOLINT(bugprone-casting-through-void)
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// copy the 16 to 32 bytes [@a p, @a p + @a n) to @a d with two overlapping
|
||||||
|
/// vectors, unless one of them is a quote, a backslash, a control
|
||||||
|
/// character, DEL or not ASCII
|
||||||
|
static bool copy_plain_vectors(char* d, const char* p, const std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x16_t a = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p));
|
||||||
|
const uint8x16_t b = vld1q_u8(reinterpret_cast<const std::uint8_t*>(p + n - 16));
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vorrq_u8(vceqq_u8(a, vdupq_n_u8('"')), vceqq_u8(a, vdupq_n_u8('\\'))),
|
||||||
|
vorrq_u8(vceqq_u8(b, vdupq_n_u8('"')), vceqq_u8(b, vdupq_n_u8('\\')))),
|
||||||
|
vorrq_u8(vorrq_u8(vcltq_s8(vreinterpretq_s8_u8(a), vdupq_n_s8(0x20)), vcltq_s8(vreinterpretq_s8_u8(b), vdupq_n_s8(0x20))),
|
||||||
|
vorrq_u8(vceqq_u8(a, vdupq_n_u8(0x7F)), vceqq_u8(b, vdupq_n_u8(0x7F)))));
|
||||||
|
if (vmaxvq_u8(special) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vst1q_u8(reinterpret_cast<std::uint8_t*>(d), a);
|
||||||
|
vst1q_u8(reinterpret_cast<std::uint8_t*>(d + n - 16), b);
|
||||||
|
#else
|
||||||
|
const __m128i a = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i b = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p + n - 16))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i quote = _mm_set1_epi8('"');
|
||||||
|
const __m128i backslash = _mm_set1_epi8('\\');
|
||||||
|
const __m128i space = _mm_set1_epi8(0x20);
|
||||||
|
const __m128i del = _mm_set1_epi8(0x7F);
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(a, quote), _mm_cmpeq_epi8(a, backslash)),
|
||||||
|
_mm_or_si128(_mm_cmpeq_epi8(b, quote), _mm_cmpeq_epi8(b, backslash))),
|
||||||
|
_mm_or_si128(_mm_or_si128(_mm_cmplt_epi8(a, space), _mm_cmplt_epi8(b, space)),
|
||||||
|
_mm_or_si128(_mm_cmpeq_epi8(a, del), _mm_cmpeq_epi8(b, del))));
|
||||||
|
if (_mm_movemask_epi8(special) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d)), a); // NOLINT(bugprone-casting-through-void)
|
||||||
|
_mm_storeu_si128(static_cast<__m128i*>(static_cast<void*>(d + n - 16)), b); // NOLINT(bugprone-casting-through-void)
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
JSON_PRIVATE_UNLESS_TESTED:
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
/*!
|
/*!
|
||||||
@brief dump escaped string
|
@brief dump escaped string
|
||||||
@@ -690,14 +913,23 @@ class serializer
|
|||||||
@param[in] s the string to escape
|
@param[in] s the string to escape
|
||||||
|
|
||||||
Complexity: Linear in the length of string @a s.
|
Complexity: Linear in the length of string @a s.
|
||||||
|
|
||||||
|
@note kept out of line: with the short-string attempts in front of it gone
|
||||||
|
from here, compilers would otherwise inline it into @ref put_object_key
|
||||||
|
and then no longer into the loops that call that
|
||||||
*/
|
*/
|
||||||
void dump_escaped(const string_t& s)
|
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s)
|
||||||
{
|
{
|
||||||
// Short strings of printable ASCII - most object keys and many values -
|
// Short strings of printable ASCII - most object keys and many values -
|
||||||
// are checked byte by byte against a table and then copied in one go.
|
// are checked byte by byte against a table and then copied in one go.
|
||||||
// For them, this is cheaper than starting the vector scanner below,
|
// For them, this is cheaper than starting the vector scanner below,
|
||||||
// whose setup costs more than such a string takes to scan. The same
|
// whose setup costs more than such a string takes to scan. The same
|
||||||
// check serves both values of ensure_ascii.
|
// check serves both values of ensure_ascii.
|
||||||
|
//
|
||||||
|
// Where put_short_quoted() exists and takes strings of up to 32 bytes,
|
||||||
|
// the callers have tried it already, and repeating the attempt only
|
||||||
|
// costs time for strings that need escaping or are not ASCII.
|
||||||
|
#if !JSON_LITTLE_ENDIAN_TARGET || !JSON_SCAN_VECTOR
|
||||||
if (s.size() <= 32)
|
if (s.size() <= 32)
|
||||||
{
|
{
|
||||||
const auto& copyable = ascii_copyable();
|
const auto& copyable = ascii_copyable();
|
||||||
@@ -719,6 +951,7 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// dispatch once here rather than test the flag inside the loop: it does
|
// dispatch once here rather than test the flag inside the loop: it does
|
||||||
// not change while a string is written, and folding it lets each of the
|
// not change while a string is written, and folding it lets each of the
|
||||||
@@ -796,7 +1029,7 @@ class serializer
|
|||||||
put_buffer(string_buffer, bytes);
|
put_buffer(string_buffer, bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
put_string(s, i, i + run);
|
put_run(s, i, run);
|
||||||
bytes_after_last_accept = 0;
|
bytes_after_last_accept = 0;
|
||||||
undumped_chars = 0;
|
undumped_chars = 0;
|
||||||
i += run;
|
i += run;
|
||||||
@@ -836,6 +1069,27 @@ class serializer
|
|||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||||
|
|
||||||
|
if (EnsureAscii && state == UTF8_ACCEPT && byte >= 0x80)
|
||||||
|
{
|
||||||
|
// a run of well-formed multi-byte characters is escaped
|
||||||
|
// straight into the write buffer; an ill-formed sequence
|
||||||
|
// ends the run and is left to the byte-at-a-time path
|
||||||
|
if (bytes != 0)
|
||||||
|
{
|
||||||
|
put_buffer(string_buffer, bytes);
|
||||||
|
bytes = 0;
|
||||||
|
bytes_after_last_accept = 0;
|
||||||
|
}
|
||||||
|
const std::size_t escaped = write_u_escapes(reinterpret_cast<const unsigned char*>(s.data()) + i, s.size() - i);
|
||||||
|
if (escaped != 0)
|
||||||
|
{
|
||||||
|
bytes_after_last_accept = 0;
|
||||||
|
undumped_chars = 0;
|
||||||
|
i += escaped - 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (decode(state, codepoint, byte))
|
switch (decode(state, codepoint, byte))
|
||||||
{
|
{
|
||||||
case UTF8_ACCEPT: // decode found a new code point
|
case UTF8_ACCEPT: // decode found a new code point
|
||||||
@@ -1113,6 +1367,12 @@ class serializer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the usual indentation, without a call of memset
|
||||||
|
if (JSON_HEDLEY_LIKELY(put_short_literal_indent("", indent)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const std::size_t capacity = write_buffer.size();
|
const std::size_t capacity = write_buffer.size();
|
||||||
|
|
||||||
// fill whatever room is left in the buffer; this is the whole job
|
// fill whatever room is left in the buffer; this is the whole job
|
||||||
@@ -1146,6 +1406,51 @@ class serializer
|
|||||||
write_buffer_pos = indent;
|
write_buffer_pos = indent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append a string literal and then @a indent indentation characters
|
||||||
|
|
||||||
|
The usual line break and indentation go into the buffer with one check of
|
||||||
|
its room and four stores of the indentation, see @ref put_short_literal_indent.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE void put_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_LIKELY(put_short_literal_indent(s, indent)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
put_literal(s);
|
||||||
|
put_indent(indent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append a string literal and up to 32 indentation characters in one go
|
||||||
|
|
||||||
|
Four words of the indentation character are stored unconditionally and only
|
||||||
|
the part wanted is claimed, which spares the call of memset. Shared by
|
||||||
|
@ref put_indent (with an empty literal, whose copy compiles to nothing) and
|
||||||
|
@ref put_literal_indent. Returns false, having written nothing, if
|
||||||
|
@a indent is wider or the write buffer lacks the room.
|
||||||
|
*/
|
||||||
|
template<std::size_t N>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool put_short_literal_indent(const char (&s)[N], const std::size_t indent) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
{
|
||||||
|
constexpr std::size_t length = N - 1;
|
||||||
|
if (indent > 32 || write_buffer_pos + length + 32 > write_buffer.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const std::uint64_t word = 0x0101010101010101ull * static_cast<unsigned char>(indent_char);
|
||||||
|
char* const d = write_buffer.data() + write_buffer_pos;
|
||||||
|
std::memcpy(d, s, length);
|
||||||
|
std::memcpy(d + length, &word, sizeof(word));
|
||||||
|
std::memcpy(d + length + 8, &word, sizeof(word));
|
||||||
|
std::memcpy(d + length + 16, &word, sizeof(word));
|
||||||
|
std::memcpy(d + length + 24, &word, sizeof(word));
|
||||||
|
write_buffer_pos += length + indent;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief append a string literal to the write buffer
|
@brief append a string literal to the write buffer
|
||||||
|
|
||||||
@@ -1170,6 +1475,28 @@ class serializer
|
|||||||
write_buffer_pos += length;
|
write_buffer_pos += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append the characters of @a str in [@a start, @a start + @a length)
|
||||||
|
|
||||||
|
A run of up to 16 characters with 16 or more in the string from @a start on
|
||||||
|
and in the write buffer from its end on is copied as a whole block of 16,
|
||||||
|
which spares the call of memcpy with a length known only at run time; the
|
||||||
|
surplus characters in the buffer are overwritten by what follows.
|
||||||
|
*/
|
||||||
|
template<typename StringType>
|
||||||
|
void put_run(const StringType& str, const std::size_t start, const std::size_t length)
|
||||||
|
{
|
||||||
|
// a short run is the exception in text without escapes (CJK, accented
|
||||||
|
// Latin), for which this keeps the long path the fast one
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(length <= 16 && start + 16 <= str.size() && write_buffer_pos + 16 <= write_buffer.size()))
|
||||||
|
{
|
||||||
|
std::memcpy(write_buffer.data() + write_buffer_pos, str.data() + start, 16);
|
||||||
|
write_buffer_pos += length;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
put_string(str, start, start + length);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief append the characters of @a str in [@a start, @a end)
|
@brief append the characters of @a str in [@a start, @a end)
|
||||||
|
|
||||||
@@ -1412,6 +1739,78 @@ class serializer
|
|||||||
return escapes;
|
return escapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief escape the well-formed multi-byte characters at the start of @a data
|
||||||
|
|
||||||
|
Writes the "\\uXXXX" escape of each character (two of them for a character
|
||||||
|
above U+FFFF) straight into the write buffer, which is why the caller must
|
||||||
|
have written out what is pending in @ref string_buffer. Stops at the first
|
||||||
|
byte that is below 0x80 or starts an ill-formed or truncated sequence, which
|
||||||
|
the caller handles.
|
||||||
|
|
||||||
|
@return the number of bytes of @a data that have been escaped
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NEVER_INLINE std::size_t write_u_escapes(const unsigned char* data, std::size_t n)
|
||||||
|
{
|
||||||
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||||
|
std::size_t i = 0;
|
||||||
|
while (i < n && data[i] >= 0x80)
|
||||||
|
{
|
||||||
|
const std::size_t length = validate_one_utf8(data + i, n - i);
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a lead byte of 0x80 or above starts a sequence of 2 to 4 bytes
|
||||||
|
JSON_ASSERT(length >= 2 && length <= 4);
|
||||||
|
|
||||||
|
const auto b0 = static_cast<std::uint32_t>(data[i]);
|
||||||
|
const auto b1 = static_cast<std::uint32_t>(data[i + 1]);
|
||||||
|
std::uint32_t codepoint = 0;
|
||||||
|
switch (length)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
codepoint = ((b0 & 0x1Fu) << 6u) | (b1 & 0x3Fu);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
codepoint = ((b0 & 0x0Fu) << 12u) | ((b1 & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu);
|
||||||
|
break;
|
||||||
|
default: // 4
|
||||||
|
codepoint = ((b0 & 0x07u) << 18u) | ((b1 & 0x3Fu) << 12u) | ((static_cast<std::uint32_t>(data[i + 2]) & 0x3Fu) << 6u) | (static_cast<std::uint32_t>(data[i + 3]) & 0x3Fu);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// at most two escapes of six bytes each
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < 12))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
char* d = write_buffer.data() + write_buffer_pos;
|
||||||
|
if (codepoint > 0xFFFFu)
|
||||||
|
{
|
||||||
|
d = put_u_escape(d, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)), nibble_to_hex);
|
||||||
|
codepoint = 0xDC00u + (codepoint & 0x3FFu);
|
||||||
|
}
|
||||||
|
d = put_u_escape(d, static_cast<std::uint16_t>(codepoint), nibble_to_hex);
|
||||||
|
write_buffer_pos = static_cast<std::size_t>(d - write_buffer.data());
|
||||||
|
i += length;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* put_u_escape(char* d, const std::uint16_t codeunit, const char* nibble_to_hex) noexcept
|
||||||
|
{
|
||||||
|
const auto unit = static_cast<std::uint32_t>(codeunit);
|
||||||
|
d[0] = '\\';
|
||||||
|
d[1] = 'u';
|
||||||
|
d[2] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||||
|
d[3] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||||
|
d[4] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||||
|
d[5] = nibble_to_hex[unit & 0x0Fu];
|
||||||
|
return d + 6;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||||
*
|
*
|
||||||
@@ -1769,11 +2168,22 @@ class serializer
|
|||||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||||
output_adapter_protocol<char>* o = nullptr;
|
output_adapter_protocol<char>* o = nullptr;
|
||||||
|
|
||||||
|
/// a character buffer left uninitialized: the buffers below are written
|
||||||
|
/// before they are read (only the part written is ever read), and zeroing
|
||||||
|
/// 1.6 KiB took more than half of what dumping a small value costs (the
|
||||||
|
/// user-provided constructor keeps the member initialization, which
|
||||||
|
/// -Weffc++ asks for, from zeroing them)
|
||||||
|
template<std::size_t N>
|
||||||
|
struct uninitialized_buffer : std::array<char, N>
|
||||||
|
{
|
||||||
|
uninitialized_buffer() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||||
|
};
|
||||||
|
|
||||||
/// a (hopefully) large enough character buffer
|
/// a (hopefully) large enough character buffer
|
||||||
std::array<char, 64> number_buffer{{}};
|
uninitialized_buffer<64> number_buffer;
|
||||||
|
|
||||||
/// string buffer
|
/// string buffer
|
||||||
std::array<char, 512> string_buffer{{}};
|
uninitialized_buffer<512> string_buffer;
|
||||||
|
|
||||||
/// the indentation character
|
/// the indentation character
|
||||||
const char indent_char;
|
const char indent_char;
|
||||||
@@ -1793,7 +2203,7 @@ class serializer
|
|||||||
/// buffer collecting output before it is flushed to the output adapter, so
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
/// that the many small structural writes become few bulk writes
|
/// that the many small structural writes become few bulk writes
|
||||||
static constexpr std::size_t write_buffer_size = 1024;
|
static constexpr std::size_t write_buffer_size = 1024;
|
||||||
std::array<char, write_buffer_size> write_buffer{{}};
|
uninitialized_buffer<write_buffer_size> write_buffer;
|
||||||
/// number of valid bytes currently held in @ref write_buffer
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
std::size_t write_buffer_pos = 0;
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string, to_string
|
||||||
|
#include <utility> // move
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|||||||
@@ -275,11 +275,34 @@ class builder
|
|||||||
0, 0, '\b', 0, 0, 0, '\f', 0, 0, 0, 0, 0, 0, 0, '\n', 0, 0, 0, '\r', 0, '\t', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
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();
|
const std::size_t start = arena_used();
|
||||||
arena_run(s, static_cast<std::size_t>(p - s));
|
char* w = m_arena_write;
|
||||||
|
const char* w_end = m_arena_end;
|
||||||
|
const arena_write_back write_back(*this, w);
|
||||||
|
{
|
||||||
|
const auto n = static_cast<std::size_t>(p - s);
|
||||||
|
if (n <= 16 && end - s >= 16)
|
||||||
|
{
|
||||||
|
arena_ensure(w, w_end, 16);
|
||||||
|
std::memcpy(w, s, 16);
|
||||||
|
}
|
||||||
|
else if (n != 0)
|
||||||
|
{
|
||||||
|
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||||
|
// must not be passed its null pointer even for zero bytes)
|
||||||
|
arena_ensure(w, w_end, n);
|
||||||
|
std::memcpy(w, s, n);
|
||||||
|
}
|
||||||
|
w += n;
|
||||||
|
}
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (p == m_end)
|
if (p == end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -287,7 +310,9 @@ class builder
|
|||||||
if (c == '"')
|
if (c == '"')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
return decoded{p, start, arena_used() - start};
|
// (from w: `write_back` stores it only after the return value
|
||||||
|
// is made; the arena is allocated, since an escape was written)
|
||||||
|
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
|
||||||
}
|
}
|
||||||
if (c != '\\')
|
if (c != '\\')
|
||||||
{
|
{
|
||||||
@@ -297,7 +322,7 @@ class builder
|
|||||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||||
}
|
}
|
||||||
++p;
|
++p;
|
||||||
if (p == m_end)
|
if (p == end)
|
||||||
{
|
{
|
||||||
return failed(error_code::string_missing_quote, p);
|
return failed(error_code::string_missing_quote, p);
|
||||||
}
|
}
|
||||||
@@ -315,7 +340,7 @@ class builder
|
|||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_low, p);
|
return failed(error_code::string_surrogate_low, p);
|
||||||
}
|
}
|
||||||
if (m_end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||||
{
|
{
|
||||||
return failed(error_code::string_surrogate_high, p);
|
return failed(error_code::string_surrogate_high, p);
|
||||||
}
|
}
|
||||||
@@ -331,26 +356,106 @@ class builder
|
|||||||
}
|
}
|
||||||
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
|
||||||
}
|
}
|
||||||
arena_ensure(4);
|
arena_ensure(w, w_end, 4);
|
||||||
m_arena_write = put_utf8(m_arena_write, cp);
|
w = put_utf8(w, cp);
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||||
{
|
{
|
||||||
arena_ensure(1);
|
arena_ensure(w, w_end, 1);
|
||||||
*m_arena_write++ = simple_escape[d];
|
*w++ = simple_escape[d];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
--p;
|
--p;
|
||||||
return failed(error_code::string_escape, p);
|
return failed(error_code::string_escape, p);
|
||||||
}
|
}
|
||||||
if (p != m_end && *p == '\\')
|
#if NLOHMANN_VIEW_NEON
|
||||||
|
if (p != end && *p == '\\')
|
||||||
{
|
{
|
||||||
continue; // consecutive escapes ("\u00e4\u00f6"): no run in between
|
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;
|
const unsigned char* const r = p;
|
||||||
p = scan_string_run(p, m_end);
|
p = scan_string_run(p, end);
|
||||||
arena_run(r, static_cast<std::size_t>(p - r));
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,19 +497,49 @@ class builder
|
|||||||
char* m_arena_write = nullptr;
|
char* m_arena_write = nullptr;
|
||||||
char* m_arena_end = 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
|
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;
|
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)
|
NLOHMANN_VIEW_NOINLINE void arena_grow(std::size_t n)
|
||||||
{
|
{
|
||||||
const std::size_t used = arena_used();
|
const std::size_t used = arena_used();
|
||||||
@@ -413,25 +548,6 @@ class builder
|
|||||||
m_arena_end = m_doc.arena.data() + m_doc.arena.size();
|
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
|
/// 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
|
/// it. (A returned pointer stays in a register; writing through a
|
||||||
/// reference to m_arena_write would store and reload it around every byte.)
|
/// reference to m_arena_write would store and reload it around every byte.)
|
||||||
@@ -954,11 +1070,11 @@ indent_done:
|
|||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
const unsigned char* const int_start = p;
|
const unsigned char* const int_start = p;
|
||||||
if (p != e && *p == '0')
|
if (cur() == '0')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
else if (NLOHMANN_VIEW_LIKELY(p != e && *p >= '1' && *p <= '9'))
|
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||||
{
|
{
|
||||||
p = skip_digits(p + 1, e);
|
p = skip_digits(p + 1, e);
|
||||||
}
|
}
|
||||||
@@ -969,7 +1085,7 @@ indent_done:
|
|||||||
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
const auto int_digits = static_cast<std::size_t>(p - int_start);
|
||||||
std::size_t frac_digits = 0;
|
std::size_t frac_digits = 0;
|
||||||
bool is_float = false;
|
bool is_float = false;
|
||||||
if (p != e && *p == '.')
|
if (cur() == '.')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
const unsigned char* const f0 = p;
|
const unsigned char* const f0 = p;
|
||||||
@@ -982,20 +1098,20 @@ indent_done:
|
|||||||
is_float = true;
|
is_float = true;
|
||||||
}
|
}
|
||||||
std::int64_t exponent = 0;
|
std::int64_t exponent = 0;
|
||||||
if (p != e && (*p | 0x20) == 'e')
|
if ((cur() | 0x20) == 'e')
|
||||||
{
|
{
|
||||||
++p;
|
++p;
|
||||||
bool exp_negative = false;
|
bool exp_negative = false;
|
||||||
if (p != e && (*p == '+' || *p == '-'))
|
if (cur() == '+' || cur() == '-')
|
||||||
{
|
{
|
||||||
exp_negative = *p == '-';
|
exp_negative = *p == '-';
|
||||||
++p;
|
++p;
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(p == e || !is_digit(*p)))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_digit(cur())))
|
||||||
{
|
{
|
||||||
return fail(error_code::number_after_exponent);
|
return fail(error_code::number_after_exponent);
|
||||||
}
|
}
|
||||||
while (p != e && is_digit(*p))
|
while (is_digit(cur()))
|
||||||
{
|
{
|
||||||
if (exponent < 100000)
|
if (exponent < 100000)
|
||||||
{
|
{
|
||||||
@@ -1057,7 +1173,7 @@ indent_done:
|
|||||||
++p; // opening quote
|
++p; // opening quote
|
||||||
const unsigned char* const s = p;
|
const unsigned char* const s = p;
|
||||||
p = scan_string_run<Value>(p, e);
|
p = scan_string_run<Value>(p, e);
|
||||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
if (NLOHMANN_VIEW_LIKELY(cur() == '"'))
|
||||||
{
|
{
|
||||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||||
++p;
|
++p;
|
||||||
|
|||||||
@@ -124,11 +124,21 @@ struct throwing_sax
|
|||||||
|
|
||||||
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
/// (a template, as in json_sax_dom_parser: the parser passes the concrete
|
||||||
/// exception type, which is thrown as is)
|
/// exception type, which is thrown as is)
|
||||||
|
///
|
||||||
|
/// It is not [[noreturn]]: sax_parse() recovers from an error if
|
||||||
|
/// parse_error() returns true, and MSVC reports that code as unreachable
|
||||||
|
/// (C4702) for a handler that never returns. The flag hides that from it.
|
||||||
template<class Exception>
|
template<class Exception>
|
||||||
[[noreturn]] bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const Exception& ex)
|
||||||
|
{
|
||||||
|
if (throwing)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(ex);
|
NLOHMANN_VIEW_THROW(ex);
|
||||||
}
|
}
|
||||||
|
return false; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
bool throwing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
|
|
||||||
#include <algorithm> // max
|
#include <algorithm> // max
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#ifndef JSON_NO_IO
|
||||||
|
#include <cstdio> // FILE, fread
|
||||||
|
#endif
|
||||||
#include <istream> // istream, streambuf
|
#include <istream> // istream, streambuf
|
||||||
#include <string> // basic_string, char_traits, string
|
#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
|
#include <type_traits> // decay, integral_constant, is_array, is_const, is_integral, is_lvalue_reference, is_pointer, is_same, remove_cv, remove_extent, remove_pointer, remove_reference
|
||||||
@@ -154,6 +157,37 @@ inline void append_stream(std::string& buf, std::istream& is)
|
|||||||
is.clear(is.rdstate() | std::ios::eofbit);
|
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 view
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -147,20 +147,21 @@ class view_item
|
|||||||
|
|
||||||
view_item(const iterator& it, std::size_t index)
|
view_item(const iterator& it, std::size_t index)
|
||||||
: m_it(it)
|
: 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
|
/// the member key, or the element index for arrays (as text made on the
|
||||||
|
/// first call, so that iterating over the values pays nothing for it)
|
||||||
string_view_t key() const
|
string_view_t key() const
|
||||||
{
|
{
|
||||||
if (m_it.is_object_iterator())
|
if (m_it.is_object_iterator())
|
||||||
{
|
{
|
||||||
return m_it.key();
|
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());
|
return string_view_t(m_index.data(), m_index.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +184,8 @@ class view_item
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
iterator m_it;
|
iterator m_it;
|
||||||
std::string m_index{}; // NOLINT(readability-redundant-member-init)
|
std::size_t m_position; ///< the index of an array element
|
||||||
|
mutable std::string m_index{}; ///< the text of m_position, once key() was called // NOLINT(readability-redundant-member-init)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the range returned by basic_json_view::items()
|
/// the range returned by basic_json_view::items()
|
||||||
|
|||||||
@@ -166,6 +166,10 @@ const node* element_at(const document_data& d, const node* array, std::size_t id
|
|||||||
{
|
{
|
||||||
return e + idx; // a growable block: one link per element
|
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)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
e = document_data::after(e);
|
e = document_data::after(e);
|
||||||
@@ -181,6 +185,10 @@ const node* last_child(const document_data& d, const node* container) noexcept
|
|||||||
const std::size_t value_offset = container->kind == static_cast<std::uint8_t>(value_t::object) ? 1 : 0;
|
const 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* const end = navigation<Editable>::end(d, container);
|
||||||
const node* last = navigation<Editable>::first(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))
|
for (const node* c = document_data::after(last + value_offset); c != end; c = document_data::after(c + value_offset))
|
||||||
{
|
{
|
||||||
last = c;
|
last = c;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // max
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint8_t
|
#include <cstdint> // int64_t, uint8_t
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
@@ -40,12 +40,13 @@ template<typename BasicJsonType, bool Editable>
|
|||||||
BasicJsonType materialize(const document_data& d, const node* n)
|
BasicJsonType materialize(const document_data& d, const node* n)
|
||||||
{
|
{
|
||||||
using string_t = typename BasicJsonType::string_t;
|
using string_t = typename BasicJsonType::string_t;
|
||||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>>;
|
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||||
using nav = navigation<Editable>;
|
using nav = navigation<Editable>;
|
||||||
|
|
||||||
// the open containers: their next element (or key of the next member),
|
// the open containers: their next element (or key of the next member),
|
||||||
// their end, and whether they are objects; sized once from the
|
// their end, and whether they are objects; for the first 16 levels on the
|
||||||
// document's deepest nesting (for a subtree an upper bound)
|
// stack, else sized once from the document's deepest nesting (for a
|
||||||
|
// subtree an upper bound)
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
const node* pos;
|
const node* pos;
|
||||||
@@ -53,10 +54,43 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
bool object;
|
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;
|
BasicJsonType result;
|
||||||
sax_t sax(result, true);
|
sax_t sax(result, true);
|
||||||
const string_t no_token{};
|
const string_t no_token{};
|
||||||
std::vector<frame> open((std::max)(d.max_depth, std::size_t{1}));
|
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||||
|
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||||
|
std::vector<frame> deep{};
|
||||||
|
frame* open = shallow.data();
|
||||||
|
std::size_t capacity = shallow.size();
|
||||||
|
if (d.max_depth > capacity)
|
||||||
|
{
|
||||||
|
deep.resize(d.max_depth);
|
||||||
|
open = deep.data();
|
||||||
|
capacity = deep.size();
|
||||||
|
}
|
||||||
std::size_t depth = 0;
|
std::size_t depth = 0;
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
@@ -76,9 +110,13 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
|||||||
{
|
{
|
||||||
sax.start_array(n->len);
|
sax.start_array(n->len);
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(depth == open.size()))
|
if (NLOHMANN_VIEW_UNLIKELY(depth == capacity))
|
||||||
{
|
{
|
||||||
open.resize(depth * 2); // (deeper than the recorded nesting, e.g. after edits)
|
// (deeper than the recorded nesting, e.g. after edits)
|
||||||
|
deep.assign(open, open + depth);
|
||||||
|
deep.resize(depth * 2);
|
||||||
|
open = deep.data();
|
||||||
|
capacity = deep.size();
|
||||||
}
|
}
|
||||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -47,27 +47,96 @@ constexpr std::size_t index_max_displacement = 64;
|
|||||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||||
"a table must be much larger than the probe window of a lookup");
|
"a table must be much larger than the probe window of a lookup");
|
||||||
|
|
||||||
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
/// hash of a key: its bytes, eight at a time (in a fixed byte order), where the
|
||||||
|
/// last word may overlap the one before (so that no loop over single bytes is
|
||||||
|
/// needed); a key of fewer than eight bytes is read as two overlapping words of
|
||||||
|
/// four or two bytes (in the native byte order: the hash is not stored, and the
|
||||||
|
/// same on both sides of a lookup)
|
||||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
std::uint64_t h = 0x9E3779B97F4A7C15u * (n + 1);
|
||||||
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
while (n >= 8)
|
std::uint64_t w = 0;
|
||||||
|
if (n >= 8)
|
||||||
|
{
|
||||||
|
const unsigned char* const last = p + n - 8;
|
||||||
|
while (p < last)
|
||||||
{
|
{
|
||||||
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
h = (h ^ read_eight_bytes(p)) * 0xBF58476D1CE4E5B9u;
|
||||||
h ^= h >> 29u;
|
h ^= h >> 29u;
|
||||||
p += 8;
|
p += 8;
|
||||||
n -= 8;
|
|
||||||
}
|
}
|
||||||
std::uint64_t w = 0;
|
w = read_eight_bytes(last);
|
||||||
for (std::size_t i = 0; i < n; ++i)
|
}
|
||||||
|
else if (n >= 4)
|
||||||
{
|
{
|
||||||
w |= static_cast<std::uint64_t>(p[i]) << (8u * i);
|
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);
|
||||||
}
|
}
|
||||||
h = (h ^ w) * 0x94D049BB133111EBu;
|
else if (n >= 2)
|
||||||
|
{
|
||||||
|
std::uint16_t head = 0;
|
||||||
|
std::uint16_t tail = 0;
|
||||||
|
std::memcpy(&head, p, 2);
|
||||||
|
std::memcpy(&tail, p + n - 2, 2);
|
||||||
|
w = static_cast<std::uint64_t>(head) | (static_cast<std::uint64_t>(tail) << 16u);
|
||||||
|
}
|
||||||
|
else if (n == 1)
|
||||||
|
{
|
||||||
|
w = p[0];
|
||||||
|
}
|
||||||
|
// (a product only carries bits upwards, and the slot is taken from the low
|
||||||
|
// bits: the high bits of the last word are folded down before the multiplication)
|
||||||
|
h = (h ^ w ^ (w >> 29u)) * 0x94D049BB133111EBu;
|
||||||
return h ^ (h >> 31u);
|
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
|
/// build the table of a large object
|
||||||
inline void build_object_index(document_data& d, node* obj)
|
inline void build_object_index(document_data& d, node* obj)
|
||||||
{
|
{
|
||||||
@@ -132,6 +201,27 @@ inline const node* find_indexed(const document_data& d, const node* obj, const c
|
|||||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
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;
|
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)
|
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||||
{
|
{
|
||||||
const std::uint32_t s = table[slot];
|
const std::uint32_t s = table[slot];
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/view/node.hpp>
|
#include <nlohmann/detail/view/node.hpp>
|
||||||
#include <nlohmann/detail/view/number.hpp>
|
#include <nlohmann/detail/view/number.hpp>
|
||||||
|
#include <nlohmann/detail/view/simd.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -80,13 +81,6 @@ class output_buffer
|
|||||||
put(s, N - 1);
|
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
|
/// 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)
|
/// that keeps the position in a local variable (set_cursor() hands it back)
|
||||||
char* cursor() const noexcept
|
char* cursor() const noexcept
|
||||||
@@ -143,9 +137,27 @@ class output_buffer
|
|||||||
/// image_check::bounds can have others, which are then written unchanged).
|
/// 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
|
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 ones = 0x0101010101010101ull;
|
||||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
std::size_t i = 0;
|
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
{
|
{
|
||||||
const std::uint64_t v = read_eight_bytes(s + i);
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
@@ -196,6 +208,70 @@ inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
|||||||
return true;
|
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
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
/// shallow documents need no allocation. The top is a pointer, as in
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -275,6 +351,39 @@ NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexc
|
|||||||
return w + N - 1;
|
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
|
/// how the view's dump() writes a value
|
||||||
struct dump_style
|
struct dump_style
|
||||||
{
|
{
|
||||||
@@ -310,140 +419,96 @@ class view_serializer
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
|
||||||
if (!m_style.pretty)
|
|
||||||
{
|
{
|
||||||
if (m_style.ensure_ascii)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
dump_compact_ascii(root);
|
dump_ascii(root);
|
||||||
|
}
|
||||||
|
else if (m_style.pretty)
|
||||||
|
{
|
||||||
|
dump_pretty(root);
|
||||||
}
|
}
|
||||||
else if (m_style.source_numbers)
|
else if (m_style.source_numbers)
|
||||||
{
|
{
|
||||||
dump_compact<true, false>(root);
|
dump_text<true, false, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false, false>(root);
|
dump_text<false, false, false>(root);
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
struct frame
|
|
||||||
{
|
|
||||||
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 (;;)
|
|
||||||
{
|
|
||||||
// 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:
|
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
|
/// 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
|
/// by an edit), without ensure_ascii: runs without characters to escape
|
||||||
/// are copied
|
/// are copied. If validate is set, the string must be valid UTF-8: the
|
||||||
NLOHMANN_VIEW_NOINLINE void write_decoded(const node& n)
|
/// 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)
|
||||||
{
|
{
|
||||||
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const s = reinterpret_cast<const unsigned char*>(m_doc.str(n)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
for (std::size_t i = 0; i < n.len;)
|
for (std::size_t i = 0; i < n.len;)
|
||||||
{
|
{
|
||||||
const std::size_t run = plain_output_run(s + i, n.len - i);
|
const std::size_t run = validate ? string_bulk_run(s + i, n.len - i) : plain_output_run(s + i, n.len - i);
|
||||||
if (run != 0)
|
if (run != 0)
|
||||||
{
|
{
|
||||||
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
m_out.put(reinterpret_cast<const char*>(s + i), run); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
i += run;
|
i += run;
|
||||||
continue;
|
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
|
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
/// whether dump_text() copies a string or key from the source: one of
|
||||||
/// 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
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
/// each, so that the walk without ensure_ascii is compiled exactly as
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
/// before: with GCC, a condition that only folds away later changed its
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
@@ -458,13 +523,155 @@ class view_serializer
|
|||||||
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a string with ensure_ascii (dump_compact()), out of line
|
/*!
|
||||||
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
@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)
|
||||||
{
|
{
|
||||||
write_string(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
/// the copies of dump_text() that are not fixed-size moves (long
|
||||||
/// strings, or near the end of the source); out of line, so that the
|
/// 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
|
/// 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
|
NLOHMANN_VIEW_NOINLINE static void copy_long(char* to, const char* from, std::size_t n) noexcept
|
||||||
@@ -481,7 +688,7 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output (the default dump(), or with ensure_ascii)
|
@brief the output, compact (the default dump()) or indented, with or without ensure_ascii
|
||||||
|
|
||||||
The same walk as dump(), with the write position in a local variable
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
@@ -489,11 +696,11 @@ class view_serializer
|
|||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
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
|
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
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
string of the source is copied only if it has no byte >= 0x7F (see
|
string of the source is copied only if it has nothing to escape (see
|
||||||
is_ascii_without_delete()); any other string is written by write_string().
|
copied()); any other string is written by write_ascii().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers, bool Ascii>
|
template<bool SourceNumbers, bool Pretty, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_text(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
{
|
{
|
||||||
@@ -508,6 +715,16 @@ class view_serializer
|
|||||||
const char* const src_end = src + m_doc.size;
|
const char* const src_end = src + m_doc.size;
|
||||||
char* w = m_out.cursor();
|
char* w = m_out.cursor();
|
||||||
char* lim = m_out.limit();
|
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
|
// room for n bytes and the slack
|
||||||
const auto room = [&](std::size_t n)
|
const auto room = [&](std::size_t n)
|
||||||
{
|
{
|
||||||
@@ -539,6 +756,22 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
w += n;
|
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
|
// a string that is not a plain string of the source (out of line, so
|
||||||
// that the cursor stays in a register here)
|
// that the cursor stays in a register here)
|
||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
@@ -546,11 +779,11 @@ class view_serializer
|
|||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
if (enabled(Ascii))
|
if (enabled(Ascii))
|
||||||
{
|
{
|
||||||
write_ascii_string(n);
|
write_ascii(n);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_decoded(n);
|
write_decoded(n, Pretty);
|
||||||
}
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
@@ -651,6 +884,7 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
*w++ = object ? '{' : '[';
|
*w++ = object ? '{' : '[';
|
||||||
stack.push_back(cur);
|
stack.push_back(cur);
|
||||||
|
level.enter();
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
cur = frame{nav::first(m_doc, n), nav::end(m_doc, n), object};
|
||||||
@@ -693,6 +927,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.leave());
|
||||||
|
}
|
||||||
room(1);
|
room(1);
|
||||||
*w++ = cur.object ? '}' : ']';
|
*w++ = cur.object ? '}' : ']';
|
||||||
cur = stack.back();
|
cur = stack.back();
|
||||||
@@ -701,13 +939,17 @@ class view_serializer
|
|||||||
room(1);
|
room(1);
|
||||||
*w++ = ',';
|
*w++ = ',';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
newline(level.get());
|
||||||
|
}
|
||||||
const node* const at = Editable ? cur.pos : n;
|
const node* const at = Editable ? cur.pos : n;
|
||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + (Pretty ? 4 : 3));
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
copy(src + key.off, key.len);
|
copy(src + key.off, key.len);
|
||||||
w[0] = '"';
|
w[0] = '"';
|
||||||
@@ -717,9 +959,13 @@ class view_serializer
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
escaped(key);
|
escaped(key);
|
||||||
room(1);
|
room(Pretty ? 2 : 1);
|
||||||
*w++ = ':';
|
*w++ = ':';
|
||||||
}
|
}
|
||||||
|
if (Pretty)
|
||||||
|
{
|
||||||
|
*w++ = ' ';
|
||||||
|
}
|
||||||
if (Editable)
|
if (Editable)
|
||||||
{
|
{
|
||||||
n = nav::value(at + 1);
|
n = nav::value(at + 1);
|
||||||
@@ -738,69 +984,6 @@ 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
|
/// a float node as dump() writes it
|
||||||
void write_float_node(const node& n)
|
void write_float_node(const node& n)
|
||||||
{
|
{
|
||||||
@@ -911,72 +1094,6 @@ class view_serializer
|
|||||||
m_out.put(s.data(), s.size());
|
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
|
/// 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)
|
NLOHMANN_VIEW_NOINLINE static void invalid_utf8(const unsigned char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -116,6 +116,34 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned
|
|||||||
}
|
}
|
||||||
return p;
|
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
|
#endif
|
||||||
|
|
||||||
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
#if NLOHMANN_VIEW_SSSE3_DISPATCH
|
||||||
|
|||||||
+221
-57
@@ -19,6 +19,7 @@
|
|||||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||||
|
|
||||||
#include <algorithm> // all_of, find, for_each, none_of
|
#include <algorithm> // all_of, find, for_each, none_of
|
||||||
|
#include <array> // array
|
||||||
#include <cmath> // isnan
|
#include <cmath> // isnan
|
||||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
@@ -156,11 +157,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend class ::nlohmann::detail::iter_impl;
|
friend class ::nlohmann::detail::iter_impl;
|
||||||
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
template<typename BasicJsonType, typename CharType, typename OutputSinkType>
|
||||||
friend class ::nlohmann::detail::binary_writer;
|
friend class ::nlohmann::detail::binary_writer;
|
||||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
template<typename BasicJsonType, typename InputType, typename SAX, bool AllowRecovery>
|
||||||
friend class ::nlohmann::detail::binary_reader;
|
friend class ::nlohmann::detail::binary_reader;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
|
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||||
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
friend class ::nlohmann::detail::json_sax_dom_callback_parser;
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
friend struct ::nlohmann::detail::diagnostic_positions;
|
friend struct ::nlohmann::detail::diagnostic_positions;
|
||||||
@@ -6115,7 +6118,7 @@ public:
|
|||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
@@ -6133,7 +6136,7 @@ public:
|
|||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
return format == input_format_t::json
|
return format == input_format_t::json
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events
|
||||||
@@ -6172,7 +6175,7 @@ public:
|
|||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
: detail::binary_reader<basic_json, decltype(ia), SAX, true>(std::move(ia), format).sax_parse(sax, strict, tag_handler);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
@@ -6287,7 +6290,7 @@ public:
|
|||||||
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
from_ubjson/from_bjdata/from_bon8/from_bson overloads
|
||||||
|
|
||||||
Building the @ref binary_reader as a named local, rather than as a
|
Building the @ref binary_reader as a named local, rather than as a
|
||||||
temporary that @ref detail::json_sax_dom_parser::parse is called on in
|
temporary that @ref detail::json_sax_dom_binary_parser::parse is called on in
|
||||||
the same expression, avoids a false-positive cppcheck accessMoved
|
the same expression, avoids a false-positive cppcheck accessMoved
|
||||||
warning in each of the 16 callers.
|
warning in each of the 16 callers.
|
||||||
*/
|
*/
|
||||||
@@ -6298,7 +6301,7 @@ public:
|
|||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
detail::json_sax_dom_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
detail::json_sax_dom_binary_parser<basic_json, InputAdapterType> sdp(result, allow_exceptions);
|
||||||
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
binary_reader<InputAdapterType> reader(std::move(ia), format, error_handler);
|
||||||
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
if (!reader.sax_parse(&sdp, strict, tag_handler))
|
||||||
{
|
{
|
||||||
@@ -6321,7 +6324,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result, error_handler).write_cbor(j);
|
vector_writer(result, error_handler).write_cbor(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6331,7 +6334,9 @@ public:
|
|||||||
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
|
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||||
|
writer.write_cbor(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a CBOR serialization of a given JSON value
|
/// @brief create a CBOR serialization of a given JSON value
|
||||||
@@ -6339,7 +6344,9 @@ public:
|
|||||||
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
static void to_cbor(const basic_json& j, detail::output_adapter<char> o,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<char>(o, error_handler).write_cbor(j);
|
binary_writer<char> writer(o, error_handler);
|
||||||
|
writer.write_cbor(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a MessagePack serialization of a given JSON value
|
/// @brief create a MessagePack serialization of a given JSON value
|
||||||
@@ -6348,7 +6355,7 @@ public:
|
|||||||
const error_handler_t error_handler = error_handler_t::keep)
|
const error_handler_t error_handler = error_handler_t::keep)
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result, error_handler).write_msgpack(j);
|
vector_writer(result, error_handler).write_msgpack(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6358,7 +6365,9 @@ public:
|
|||||||
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||||
const error_handler_t error_handler = error_handler_t::keep)
|
const error_handler_t error_handler = error_handler_t::keep)
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
|
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||||
|
writer.write_msgpack(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a MessagePack serialization of a given JSON value
|
/// @brief create a MessagePack serialization of a given JSON value
|
||||||
@@ -6366,7 +6375,9 @@ public:
|
|||||||
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
static void to_msgpack(const basic_json& j, detail::output_adapter<char> o,
|
||||||
const error_handler_t error_handler = error_handler_t::keep)
|
const error_handler_t error_handler = error_handler_t::keep)
|
||||||
{
|
{
|
||||||
binary_writer<char>(o, error_handler).write_msgpack(j);
|
binary_writer<char> writer(o, error_handler);
|
||||||
|
writer.write_msgpack(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a UBJSON serialization of a given JSON value
|
/// @brief create a UBJSON serialization of a given JSON value
|
||||||
@@ -6377,7 +6388,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6388,7 +6399,9 @@ public:
|
|||||||
const bool use_size = false, const bool use_type = false,
|
const bool use_size = false, const bool use_type = false,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
|
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||||
|
writer.write_ubjson(j, use_size, use_type);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a UBJSON serialization of a given JSON value
|
/// @brief create a UBJSON serialization of a given JSON value
|
||||||
@@ -6397,7 +6410,9 @@ public:
|
|||||||
const bool use_size = false, const bool use_type = false,
|
const bool use_size = false, const bool use_type = false,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
|
binary_writer<char> writer(o, error_handler);
|
||||||
|
writer.write_ubjson(j, use_size, use_type);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BJData serialization of a given JSON value
|
/// @brief create a BJData serialization of a given JSON value
|
||||||
@@ -6409,7 +6424,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6421,7 +6436,9 @@ public:
|
|||||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||||
|
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BJData serialization of a given JSON value
|
/// @brief create a BJData serialization of a given JSON value
|
||||||
@@ -6431,7 +6448,9 @@ public:
|
|||||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
binary_writer<char> writer(o, error_handler);
|
||||||
|
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BSON serialization of a given JSON value
|
/// @brief create a BSON serialization of a given JSON value
|
||||||
@@ -6440,7 +6459,7 @@ public:
|
|||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result, error_handler).write_bson(j);
|
vector_writer(result, error_handler).write_bson(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6450,7 +6469,9 @@ public:
|
|||||||
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
|
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||||
|
writer.write_bson(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BSON serialization of a given JSON value
|
/// @brief create a BSON serialization of a given JSON value
|
||||||
@@ -6458,7 +6479,9 @@ public:
|
|||||||
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
static void to_bson(const basic_json& j, detail::output_adapter<char> o,
|
||||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||||
{
|
{
|
||||||
binary_writer<char>(o, error_handler).write_bson(j);
|
binary_writer<char> writer(o, error_handler);
|
||||||
|
writer.write_bson(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BON8 serialization of a given JSON value
|
/// @brief create a BON8 serialization of a given JSON value
|
||||||
@@ -6466,7 +6489,7 @@ public:
|
|||||||
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
|
||||||
{
|
{
|
||||||
std::vector<std::uint8_t> result;
|
std::vector<std::uint8_t> result;
|
||||||
result.reserve(detail::binary_reserve_hint(j));
|
result.reserve(detail::binary_initial_capacity(j));
|
||||||
vector_writer(result).write_bon8(j);
|
vector_writer(result).write_bon8(j);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -6475,14 +6498,18 @@ public:
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||||
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
static void to_bon8(const basic_json& j, detail::output_adapter<std::uint8_t> o)
|
||||||
{
|
{
|
||||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
binary_writer<std::uint8_t> writer(o);
|
||||||
|
writer.write_bon8(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a BON8 serialization of a given JSON value
|
/// @brief create a BON8 serialization of a given JSON value
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||||
{
|
{
|
||||||
binary_writer<char>(o).write_bon8(j);
|
binary_writer<char> writer(o);
|
||||||
|
writer.write_bon8(j);
|
||||||
|
writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in CBOR format
|
/// @brief create a JSON value from an input in CBOR format
|
||||||
@@ -6878,6 +6905,72 @@ public:
|
|||||||
/// @name JSON Patch functions
|
/// @name JSON Patch functions
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the members of an operation of a JSON patch that are of interest
|
||||||
|
struct patch_members
|
||||||
|
{
|
||||||
|
basic_json* op = nullptr;
|
||||||
|
basic_json* path = nullptr;
|
||||||
|
basic_json* value = nullptr;
|
||||||
|
basic_json* from = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief find the members "op", "path", "value", and "from" of the object @a val in one pass
|
||||||
|
static patch_members find_patch_members(const basic_json& val, std::true_type /*key_type is string_t*/)
|
||||||
|
{
|
||||||
|
patch_members members;
|
||||||
|
for (auto& member : *val.m_data.m_value.object)
|
||||||
|
{
|
||||||
|
const string_t& key = member.first;
|
||||||
|
switch (key.size())
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
if (key == "op")
|
||||||
|
{
|
||||||
|
members.op = &member.second;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (key == "path")
|
||||||
|
{
|
||||||
|
members.path = &member.second;
|
||||||
|
}
|
||||||
|
else if (key == "from")
|
||||||
|
{
|
||||||
|
members.from = &member.second;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
if (key == "value")
|
||||||
|
{
|
||||||
|
members.value = &member.second;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief find the members by lookup for key types other than string_t
|
||||||
|
static patch_members find_patch_members(const basic_json& val, std::false_type /*key_type is not string_t*/)
|
||||||
|
{
|
||||||
|
const auto find_member = [&val](const char* name) -> basic_json *
|
||||||
|
{
|
||||||
|
const auto it = val.m_data.m_value.object->find(string_t(name));
|
||||||
|
return it != val.m_data.m_value.object->end() ? &it->second : nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
patch_members members;
|
||||||
|
members.op = find_member("op");
|
||||||
|
members.path = find_member("path");
|
||||||
|
members.value = find_member("value");
|
||||||
|
members.from = find_member("from");
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
/// @brief applies a JSON patch in-place without copying the object
|
/// @brief applies a JSON patch in-place without copying the object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||||
void patch_inplace(const basic_json& json_patch)
|
void patch_inplace(const basic_json& json_patch)
|
||||||
@@ -7051,18 +7144,18 @@ public:
|
|||||||
// a diff creates them: apply the operation and return true; return
|
// a diff creates them: apply the operation and return true; return
|
||||||
// false, with nothing changed, for all others
|
// false, with nothing changed, for all others
|
||||||
string_t token;
|
string_t token;
|
||||||
const string_t value_key = "value";
|
typename json_pointer::template plain_steps<basic_json> steps;
|
||||||
const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
|
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||||
{
|
{
|
||||||
if (path.empty() || path.find_first_of('~') != string_t::npos)
|
if (path.empty())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a missing "value" is reported below
|
// a missing "value" is reported below
|
||||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
const basic_json* const value_ptr = members.value;
|
||||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -7071,7 +7164,7 @@ public:
|
|||||||
{
|
{
|
||||||
case patch_operations::replace:
|
case patch_operations::replace:
|
||||||
{
|
{
|
||||||
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
if (target == nullptr)
|
if (target == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -7082,7 +7175,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::test:
|
case patch_operations::test:
|
||||||
{
|
{
|
||||||
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
|
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps);
|
||||||
// a failing test is reported below
|
// a failing test is reported below
|
||||||
return target != nullptr && *target == value;
|
return target != nullptr && *target == value;
|
||||||
}
|
}
|
||||||
@@ -7102,13 +7195,17 @@ public:
|
|||||||
}
|
}
|
||||||
--slash;
|
--slash;
|
||||||
|
|
||||||
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
|
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps);
|
||||||
if (parent == nullptr)
|
if (parent == nullptr)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
token.clear();
|
token.clear();
|
||||||
token.append(path.data() + slash + 1, path.size() - slash - 1);
|
token.append(path.data() + slash + 1, path.size() - slash - 1);
|
||||||
|
if (token.find_first_of('~') != string_t::npos)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (operation == patch_operations::add)
|
if (operation == patch_operations::add)
|
||||||
{
|
{
|
||||||
@@ -7164,31 +7261,32 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// the name of the first member to check
|
||||||
|
const string_t op_member = "op";
|
||||||
|
|
||||||
// iterate and apply the operations
|
// iterate and apply the operations
|
||||||
for (const auto& val : json_patch)
|
for (const auto& val : json_patch)
|
||||||
{
|
{
|
||||||
// wrapper to get a value for an operation
|
// wrapper to get a value for an operation
|
||||||
const auto get_value = [&val](const string_t& op,
|
const auto get_value = [&val](basic_json * found,
|
||||||
const string_t& member,
|
const string_t& op,
|
||||||
|
const char* member,
|
||||||
bool string_type) -> basic_json &
|
bool string_type) -> basic_json &
|
||||||
{
|
{
|
||||||
// find value
|
|
||||||
auto it = val.m_data.m_value.object->find(member);
|
|
||||||
|
|
||||||
// check if the desired value is present
|
// check if the desired value is present
|
||||||
if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
|
if (JSON_HEDLEY_UNLIKELY(found == nullptr))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, false, val);
|
detail::throw_patch_missing_member(op, string_t(member), false, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the result is of type string
|
// check if the result is of type string
|
||||||
if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
|
if (JSON_HEDLEY_UNLIKELY(string_type && !found->is_string()))
|
||||||
{
|
{
|
||||||
detail::throw_patch_missing_member(op, member, true, val);
|
detail::throw_patch_missing_member(op, string_t(member), true, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no error: return value
|
// no error: return value
|
||||||
return it->second;
|
return *found;
|
||||||
};
|
};
|
||||||
|
|
||||||
// type check: every element of the array must be an object
|
// type check: every element of the array must be an object
|
||||||
@@ -7197,26 +7295,30 @@ public:
|
|||||||
detail::throw_patch_not_an_array(val);
|
detail::throw_patch_not_an_array(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const patch_members members = find_patch_members(val, std::is_same<typename object_t::key_type, string_t> {});
|
||||||
|
|
||||||
// collect mandatory members
|
// collect mandatory members
|
||||||
const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
|
const string_t& op = *get_value(members.op, op_member, "op", true).m_data.m_value.string;
|
||||||
const string_t& path = *get_value(op, "path", true).m_data.m_value.string;
|
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string;
|
||||||
const patch_operations operation = get_op(op);
|
const patch_operations operation = get_op(op);
|
||||||
|
|
||||||
// the usual operations on a plain pointer are applied without
|
// the usual operations on a plain pointer are applied without
|
||||||
// building the pointer's reference tokens; anything else (and
|
// building the pointer's reference tokens; anything else (and
|
||||||
// every error) is handled below
|
// every error) is handled below
|
||||||
if (try_plain(operation, path, val))
|
if (try_plain(operation, path, members, val))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the general operations may change any container
|
||||||
|
steps.reset();
|
||||||
json_pointer ptr(path);
|
json_pointer ptr(path);
|
||||||
|
|
||||||
switch (operation)
|
switch (operation)
|
||||||
{
|
{
|
||||||
case patch_operations::add:
|
case patch_operations::add:
|
||||||
{
|
{
|
||||||
operation_add(ptr, get_value("add", "value", false));
|
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7229,13 +7331,13 @@ public:
|
|||||||
case patch_operations::replace:
|
case patch_operations::replace:
|
||||||
{
|
{
|
||||||
// the "path" location must exist - use at()
|
// the "path" location must exist - use at()
|
||||||
result.at(ptr) = get_value("replace", "value", false);
|
result.at(ptr) = get_value(members.value, "replace", "value", false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case patch_operations::move:
|
case patch_operations::move:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("move", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string;
|
||||||
json_pointer from_ptr(from_path);
|
json_pointer from_ptr(from_path);
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||||
@@ -7258,7 +7360,7 @@ public:
|
|||||||
|
|
||||||
case patch_operations::copy:
|
case patch_operations::copy:
|
||||||
{
|
{
|
||||||
const string_t& from_path = *get_value("copy", "from", true).m_data.m_value.string;
|
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string;
|
||||||
const json_pointer from_ptr(from_path);
|
const json_pointer from_ptr(from_path);
|
||||||
|
|
||||||
// The copy is functionally identical to an "add"
|
// The copy is functionally identical to an "add"
|
||||||
@@ -7276,7 +7378,7 @@ public:
|
|||||||
{
|
{
|
||||||
// check if "value" matches the one at "path"
|
// check if "value" matches the one at "path"
|
||||||
// the "path" location must exist - use at()
|
// the "path" location must exist - use at()
|
||||||
success = (result.at(ptr) == get_value("test", "value", false));
|
success = (result.at(ptr) == get_value(members.value, "test", "value", false));
|
||||||
}
|
}
|
||||||
JSON_INTERNAL_CATCH (out_of_range&)
|
JSON_INTERNAL_CATCH (out_of_range&)
|
||||||
{
|
{
|
||||||
@@ -7368,13 +7470,18 @@ public:
|
|||||||
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
|
/// @brief append an operation @a op for @a path to @a result, with @a value if it is not null
|
||||||
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
|
static void diff_operation(basic_json& result, const char* op, const string_t& path, const basic_json* value)
|
||||||
{
|
{
|
||||||
|
// the keys are distinct and in the order of std::map; the members are
|
||||||
|
// added without looking them up
|
||||||
basic_json entry(value_t::object);
|
basic_json entry(value_t::object);
|
||||||
entry["op"] = op;
|
auto& members = *entry.m_data.m_value.object;
|
||||||
entry["path"] = path;
|
auto hint = members.end();
|
||||||
|
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
|
||||||
|
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
|
||||||
if (value != nullptr)
|
if (value != nullptr)
|
||||||
{
|
{
|
||||||
entry["value"] = *value;
|
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {});
|
||||||
}
|
}
|
||||||
|
entry.set_parents();
|
||||||
result.push_back(std::move(entry));
|
result.push_back(std::move(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7396,6 +7503,62 @@ public:
|
|||||||
diff_operation(result, "add", path, &value);
|
diff_operation(result, "add", path, &value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief the values in the target of the members of a source object, for the
|
||||||
|
/// objects of up to 16 members without allocating
|
||||||
|
///
|
||||||
|
/// reserve() must be called with the number of values that are added.
|
||||||
|
class diff_target_values
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
diff_target_values() noexcept
|
||||||
|
: inline_items() // NOLINT(readability-redundant-member-init)
|
||||||
|
{}
|
||||||
|
diff_target_values(const diff_target_values&) = delete;
|
||||||
|
diff_target_values& operator=(const diff_target_values&) = delete;
|
||||||
|
diff_target_values(diff_target_values&&) = delete;
|
||||||
|
diff_target_values& operator=(diff_target_values&&) = delete;
|
||||||
|
~diff_target_values() = default;
|
||||||
|
|
||||||
|
void reserve(const std::size_t n)
|
||||||
|
{
|
||||||
|
if (n > inline_size)
|
||||||
|
{
|
||||||
|
large.resize(n);
|
||||||
|
items = large.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(const basic_json* value)
|
||||||
|
{
|
||||||
|
items[count++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t size() const noexcept
|
||||||
|
{
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basic_json* operator[](const std::size_t i) const noexcept
|
||||||
|
{
|
||||||
|
return items[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::size_t inline_size = 16;
|
||||||
|
/// the values of a small object, left uninitialized: they are written
|
||||||
|
/// before they are read (the user-provided constructor keeps the
|
||||||
|
/// member initialization, which -Weffc++ asks for, from zeroing them)
|
||||||
|
struct uninitialized_items : std::array<const basic_json*, inline_size>
|
||||||
|
{
|
||||||
|
uninitialized_items() noexcept {} // NOLINT(modernize-use-equals-default,hicpp-use-equals-default,cppcoreguidelines-pro-type-member-init,hicpp-member-init): see above
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t count = 0;
|
||||||
|
std::vector<const basic_json*> large{}; // NOLINT(readability-redundant-member-init)
|
||||||
|
uninitialized_items inline_items;
|
||||||
|
const basic_json** items = inline_items.data();
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief append "/" and the decimal representation of @a index to @a path
|
/// @brief append "/" and the decimal representation of @a index to @a path
|
||||||
static void diff_append_index(string_t& path, std::size_t index)
|
static void diff_append_index(string_t& path, std::size_t index)
|
||||||
{
|
{
|
||||||
@@ -7476,8 +7639,9 @@ public:
|
|||||||
operations that remove every member of @a source and add every member of
|
operations that remove every member of @a source and add every member of
|
||||||
@a target to @a result, and returns false.
|
@a target to @a result, and returns false.
|
||||||
*/
|
*/
|
||||||
|
template<typename TargetValues>
|
||||||
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
static bool diff_object_keys(basic_json& result, const basic_json& source, const basic_json& target,
|
||||||
const string_t& path, std::vector<const basic_json*>& target_values,
|
const string_t& path, TargetValues& target_values,
|
||||||
std::vector<const_iterator>& added)
|
std::vector<const_iterator>& added)
|
||||||
{
|
{
|
||||||
// first pass: one lookup per source key
|
// first pass: one lookup per source key
|
||||||
@@ -7691,7 +7855,7 @@ public:
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
std::vector<const basic_json*> target_values;
|
diff_target_values target_values;
|
||||||
std::vector<const_iterator> added;
|
std::vector<const_iterator> added;
|
||||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
|
#include <cstdio> // FILE
|
||||||
#include <cstring> // memcpy, strlen
|
#include <cstring> // memcpy, strlen
|
||||||
#include <istream> // istream // IWYU pragma: keep
|
#include <istream> // istream // IWYU pragma: keep
|
||||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||||
@@ -295,7 +296,7 @@ class basic_json_view
|
|||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
/// any other integer type (int, unsigned, long, std::int64_t, ...; a
|
||||||
@@ -358,7 +359,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
}
|
}
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
return element(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// any other integer type, see operator[]; negative values are out of
|
/// any other integer type, see operator[]; negative values are out of
|
||||||
@@ -392,7 +393,13 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(string_view_t key, const char* default_value) const
|
string_t value(string_view_t key, const char* default_value) const
|
||||||
{
|
{
|
||||||
return value(key, string_t(default_value));
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
|
{
|
||||||
|
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||||
|
}
|
||||||
|
// (the default is made only if there is no such member)
|
||||||
|
const basic_json_view r = lookup(key);
|
||||||
|
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the value a JSON pointer refers to converted to T, or the default
|
/// the value a JSON pointer refers to converted to T, or the default
|
||||||
@@ -411,7 +418,12 @@ class basic_json_view
|
|||||||
|
|
||||||
string_t value(const json_pointer& ptr, const char* default_value) const
|
string_t value(const json_pointer& ptr, const char* default_value) const
|
||||||
{
|
{
|
||||||
return value(ptr, string_t(default_value));
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
|
{
|
||||||
|
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
||||||
|
}
|
||||||
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||||
|
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the first element or member value; a primitive value itself. Throws
|
/// the first element or member value; a primitive value itself. Throws
|
||||||
@@ -433,7 +445,8 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (is_structured() && m_node->len != 0)
|
if (is_structured() && m_node->len != 0)
|
||||||
{
|
{
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
return unedited() ? basic_json_view(m_doc, detail::view::last_child<false>(*m_doc, m_node) + (is_object() ? 1 : 0))
|
||||||
|
: basic_json_view(m_doc, navigation::value(detail::view::last_child<Editable>(*m_doc, m_node) + (is_object() ? 1 : 0)));
|
||||||
}
|
}
|
||||||
return front();
|
return front();
|
||||||
}
|
}
|
||||||
@@ -450,7 +463,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return end();
|
return end();
|
||||||
}
|
}
|
||||||
const node* const k = detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size());
|
const node* const k = find_key(key);
|
||||||
return k != nullptr ? iterator(m_doc, k, true) : end();
|
return k != nullptr ? iterator(m_doc, k, true) : end();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +480,7 @@ class basic_json_view
|
|||||||
/// whether this is an object with a member with this key
|
/// whether this is an object with a member with this key
|
||||||
bool contains(string_view_t key) const
|
bool contains(string_view_t key) const
|
||||||
{
|
{
|
||||||
return is_object() && detail::view::find_member<Editable>(*m_doc, m_node, key.data(), key.size()) != nullptr;
|
return is_object() && find_key(key) != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contains(const char* key) const
|
bool contains(const char* key) const
|
||||||
@@ -627,7 +640,14 @@ class basic_json_view
|
|||||||
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
// the compact writer keeps 64 bytes of slack, so that it does not grow
|
||||||
// the buffer just before the end
|
// the buffer just before the end
|
||||||
const std::size_t estimate = source_extent() + (style.pretty ? source_extent() / 2 : 0) + 160;
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,7 +727,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return BasicJsonType(value_t::discarded);
|
return BasicJsonType(value_t::discarded);
|
||||||
}
|
}
|
||||||
return detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
return unedited() ? detail::view::materialize<BasicJsonType, false>(*m_doc, m_node) : detail::view::materialize<BasicJsonType, Editable>(*m_doc, m_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// byte offset of this value in the source text (for strings: of the
|
/// byte offset of this value in the source text (for strings: of the
|
||||||
@@ -785,10 +805,50 @@ class basic_json_view
|
|||||||
/// (object required)
|
/// (object required)
|
||||||
NLOHMANN_VIEW_ALWAYS_INLINE basic_json_view lookup(string_view_t key) const noexcept
|
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());
|
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();
|
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 ---
|
// --- get() dispatch ---
|
||||||
|
|
||||||
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
bool get_impl(detail::view::value_tag<bool> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||||
@@ -1528,7 +1588,8 @@ class basic_json_document
|
|||||||
// NOLINTBEGIN(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
// 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
|
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
|
: std::is_base_of<std::istream, D>::value ? 1
|
||||||
: detail::view::is_adapter_input<D>::value ? 2 : 3 > {});
|
: detail::view::is_adapter_input<D>::value ? 2
|
||||||
|
: detail::view::is_c_stream<D>::value ? 4 : 3 > {});
|
||||||
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
// NOLINTEND(readability-avoid-nested-conditional-operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1550,6 +1611,19 @@ class basic_json_document
|
|||||||
detail::view::append_adapter(buf, std::move(input.ia));
|
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>
|
template<typename T>
|
||||||
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
static void append_input_impl(std::string& buf, T&& input, std::integral_constant<int, 3> /*other*/)
|
||||||
{
|
{
|
||||||
|
|||||||
Generated
+6840
-675
File diff suppressed because it is too large.
Load diff
Generated
+849
-331
File diff suppressed because it is too large.
Load diff
@@ -60,6 +60,7 @@ inline namespace json_literals
|
|||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
using NLOHMANN_JSON_NAMESPACE::detail::json_sax_dom_callback_parser;
|
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;
|
using NLOHMANN_JSON_NAMESPACE::detail::unknown_size;
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ dumps is stable under exactly the same values that break operator==.
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bjdata() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -57,6 +61,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -81,6 +86,9 @@ static bool is_value_stable(const json& lhs, const json& rhs)
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bjdata).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -99,6 +107,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2.1: round trip without adding size annotations to container types
|
// step 2.1: round trip without adding size annotations to container types
|
||||||
@@ -132,6 +143,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -142,6 +154,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bon8(vec)
|
- j2 = from_bon8(vec)
|
||||||
- assert(to_bon8(j2) == vec)
|
- assert(to_bon8(j2) == vec)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bon8() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_bon8
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bon8).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_bson(vec)
|
- j2 = from_bson(vec)
|
||||||
- assert(to_bson(j2) == vec)
|
- assert(to_bson(j2) == vec)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_bson() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_bson
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::bson).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_cbor(vec)
|
- j2 = from_cbor(vec)
|
||||||
- assert(to_cbor(j2) == vec)
|
- assert(to_cbor(j2) == vec)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_cbor() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_cbor
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::cbor).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors can occur during parsing, too
|
// out of range errors can occur during parsing, too
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ array data, it performs the following steps:
|
|||||||
The pointer range takes the bulk fast paths, the other input adapters read byte
|
The pointer range takes the bulk fast paths, the other input adapters read byte
|
||||||
by byte; see check_input_adapters().
|
by byte; see check_input_adapters().
|
||||||
|
|
||||||
|
Furthermore, it parses data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that parsing ends, and that valid
|
||||||
|
input is parsed without errors (see #3989).
|
||||||
|
|
||||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -59,6 +64,13 @@ struct accept_json
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
{
|
||||||
|
const auto checker = check_recovering_parse(data, size, json::input_format_t::json);
|
||||||
|
assert(checker.events <= (4 * size) + 4);
|
||||||
|
assert((checker.errors == 0) == json::accept(data, data + size));
|
||||||
|
}
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
bool noexcept_threw = false;
|
bool noexcept_threw = false;
|
||||||
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
json const j_noexcept = parse_without_exceptions([&] { return json::parse(data, data + size, nullptr, false); }, noexcept_threw);
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ array data, it performs the following steps:
|
|||||||
- j2 = from_msgpack(vec)
|
- j2 = from_msgpack(vec)
|
||||||
- assert(to_msgpack(j2) == vec)
|
- assert(to_msgpack(j2) == vec)
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_msgpack() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -27,6 +31,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -44,6 +49,9 @@ struct read_msgpack
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::msgpack).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -62,6 +70,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2: round trip
|
// step 2: round trip
|
||||||
@@ -83,6 +94,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -93,6 +105,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ array data, it performs the following steps:
|
|||||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||||
invariants" test case), so keep both in sync.
|
invariants" test case), so keep both in sync.
|
||||||
|
|
||||||
|
Furthermore, it reads data with a SAX parser that recovers from every error
|
||||||
|
and checks that the events are balanced, that reading ends, and that it
|
||||||
|
reports an error exactly when from_ubjson() fails (see #3989).
|
||||||
|
|
||||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||||
@@ -36,6 +40,7 @@ drivers.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "fuzzer_common.hpp"
|
#include "fuzzer_common.hpp"
|
||||||
|
#include "fuzzer-recovering_checker.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -53,6 +58,9 @@ struct read_ubjson
|
|||||||
// see http://llvm.org/docs/LibFuzzer.html
|
// see http://llvm.org/docs/LibFuzzer.html
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
|
// recover from all errors, reading from memory and from a stream
|
||||||
|
const bool recovered_without_errors = check_recovering_parse(data, size, json::input_format_t::ubjson).errors == 0;
|
||||||
|
|
||||||
std::vector<uint8_t> const vec1(data, data + size);
|
std::vector<uint8_t> const vec1(data, data + size);
|
||||||
|
|
||||||
// step 0: parse input without exceptions
|
// step 0: parse input without exceptions
|
||||||
@@ -71,6 +79,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
// without exceptions, the same input must give the same value
|
// without exceptions, the same input must give the same value
|
||||||
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
assert(!noexcept_threw && !j_noexcept.is_discarded() && same_value(j_noexcept, j1));
|
||||||
|
|
||||||
|
// the recovering parser must not have reported an error either
|
||||||
|
assert(recovered_without_errors);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// step 2.1: round trip without adding size annotations to container types
|
// step 2.1: round trip without adding size annotations to container types
|
||||||
@@ -102,6 +113,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// parse errors are ok, because input may be random bytes
|
// parse errors are ok, because input may be random bytes
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
catch (const json::type_error&)
|
catch (const json::type_error&)
|
||||||
{
|
{
|
||||||
@@ -112,6 +124,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
|||||||
{
|
{
|
||||||
// out of range errors may happen if provided sizes are excessive
|
// out of range errors may happen if provided sizes are excessive
|
||||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||||
|
assert(parsed || !recovered_without_errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 3: the other input adapters must read the data alike
|
// step 3: the other input adapters must read the data alike
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a SAX parser that recovers from every error and checks that the events are
|
||||||
|
// balanced and that every key is followed by exactly one value
|
||||||
|
class recovering_checker : public nlohmann::json_sax<nlohmann::json>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool null() override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(number_integer_t /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(binary_t& /*val*/) override
|
||||||
|
{
|
||||||
|
return value();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(string_t& /*val*/) override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'o');
|
||||||
|
stack.back() = 'v';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object() override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'o');
|
||||||
|
stack.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t /*elements*/) override
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array() override
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
assert(!stack.empty() && stack.back() == 'a');
|
||||||
|
stack.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const nlohmann::detail::exception& /*ex*/) override
|
||||||
|
{
|
||||||
|
++errors;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool complete() const
|
||||||
|
{
|
||||||
|
return stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t events = 0;
|
||||||
|
std::size_t errors = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool value()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
// an array element, or the value of a key
|
||||||
|
assert(stack.back() != 'o');
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'a' for an array, 'o' for an object that expects a key, 'v' for an
|
||||||
|
// object that expects the value of a key
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// parses @a data with a recovering_checker from memory and from a stream,
|
||||||
|
/// checks that both see the same, that the events are balanced, and that the
|
||||||
|
/// number of errors is bounded, and returns the checker (see #3989)
|
||||||
|
inline recovering_checker check_recovering_parse(const std::uint8_t* data, const std::size_t size, const nlohmann::json::input_format_t format)
|
||||||
|
{
|
||||||
|
recovering_checker checker;
|
||||||
|
const bool ok = nlohmann::json::sax_parse(data, data + size, &checker, format);
|
||||||
|
assert(checker.complete());
|
||||||
|
assert(checker.errors <= size + 1);
|
||||||
|
assert(ok == (checker.errors == 0));
|
||||||
|
|
||||||
|
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||||
|
recovering_checker stream_checker;
|
||||||
|
assert(nlohmann::json::sax_parse(stream, &stream_checker, format) == ok);
|
||||||
|
assert(stream_checker.complete());
|
||||||
|
assert(stream_checker.events == checker.events);
|
||||||
|
assert(stream_checker.errors == checker.errors);
|
||||||
|
|
||||||
|
return checker;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
@@ -131,11 +131,13 @@ struct SaxEventLogger : public nlohmann::json_sax<nlohmann::json>
|
|||||||
{
|
{
|
||||||
errored = true;
|
errored = true;
|
||||||
events.push_back("parse_error(" + std::to_string(position) + ")");
|
events.push_back("parse_error(" + std::to_string(position) + ")");
|
||||||
return false;
|
return recover;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
std::vector<std::string> events {}; // NOLINT(readability-redundant-member-init)
|
||||||
bool errored = false;
|
bool errored = false;
|
||||||
|
/// whether parse_error() asks the parser to recover from the error (see #3989)
|
||||||
|
bool recover = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
struct SaxEventLoggerExitAfterStartObject : public SaxEventLogger
|
||||||
|
|||||||
@@ -415,6 +415,46 @@ TEST_CASE("alternative string type")
|
|||||||
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
CHECK(j2.dump() == R"({"/foo/0":"bar","/foo/1":"baz"})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("error recovery")
|
||||||
|
{
|
||||||
|
// a SAX parser that recovers from every error (see #3989)
|
||||||
|
struct recovering_parser : nlohmann::detail::json_sax_dom_parser<alt_json>
|
||||||
|
{
|
||||||
|
explicit recovering_parser(alt_json& j)
|
||||||
|
: nlohmann::detail::json_sax_dom_parser<alt_json>(j, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// sax_parse() calls the SAX parser's own parse_error(), so hiding
|
||||||
|
// the one of the base class is what recovering takes
|
||||||
|
// NOLINTNEXTLINE(bugprone-derived-method-shadowing-base-method)
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const nlohmann::detail::exception& /*unused*/)
|
||||||
|
{
|
||||||
|
++errors;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t errors = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
alt_json j;
|
||||||
|
recovering_parser sax(j);
|
||||||
|
// not inside CHECK(): MSVC reads the escape in a stringized raw string
|
||||||
|
const std::string input = R"([1., "a\qb", tru, {"k" 2}])";
|
||||||
|
CHECK(!alt_json::sax_parse(input, &sax));
|
||||||
|
CHECK(sax.errors == 4);
|
||||||
|
CHECK(j.dump() == R"([1,"aqb",null,{"k":2}])");
|
||||||
|
|
||||||
|
// a UBJSON high-precision number, a CBOR key that is not a string
|
||||||
|
alt_json u;
|
||||||
|
recovering_parser ubjson_sax(u);
|
||||||
|
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {'[', 'H', 'i', 2, '1', '.', ']'}, &ubjson_sax, alt_json::input_format_t::ubjson));
|
||||||
|
CHECK(u.dump() == "[1]");
|
||||||
|
alt_json c;
|
||||||
|
recovering_parser cbor_sax(c);
|
||||||
|
CHECK(!alt_json::sax_parse(std::vector<std::uint8_t> {0xA2, 0x01, 0x02, 0x61, 'a', 0x03}, &cbor_sax, alt_json::input_format_t::cbor));
|
||||||
|
CHECK(c.dump() == R"({"a":3})");
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("conversion between basic_json specializations (#2649)")
|
SECTION("conversion between basic_json specializations (#2649)")
|
||||||
{
|
{
|
||||||
// explicit conversions are always possible
|
// explicit conversions are always possible
|
||||||
|
|||||||
@@ -492,3 +492,278 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
|||||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
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
|
||||||
@@ -2952,3 +2952,726 @@ TEST_CASE("diagnostic positions: value lifetime, input adapters, and SAX")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// builds a value like json::parse(), but asks the parser to recover from
|
||||||
|
/// errors (see #3989), and checks that the events it receives are balanced
|
||||||
|
class RecoveringDomParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RecoveringDomParser(json& j, std::size_t max_errors_ = static_cast<std::size_t>(-1))
|
||||||
|
: dom(j, false)
|
||||||
|
, max_errors(max_errors_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null()
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.boolean(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(json::number_integer_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_integer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(json::number_unsigned_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_unsigned(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(json::number_float_t val, const std::string& s)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_float(val, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.string(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(json::binary_t& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.binary(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return dom.start_object(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val)
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.back() = 'v';
|
||||||
|
return dom.key(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return dom.start_array(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (stack.empty() || stack.back() != 'a')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||||
|
{
|
||||||
|
errors.emplace_back(ex.what());
|
||||||
|
return errors.size() < max_errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the events were balanced and every key was followed by a value
|
||||||
|
bool balanced() const
|
||||||
|
{
|
||||||
|
return well_formed && stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the value
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json> dom;
|
||||||
|
std::vector<std::string> errors {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::size_t events = 0;
|
||||||
|
/// the open containers: 'a' for an array, 'o' for an object that expects
|
||||||
|
/// a key, 'v' for an object that expects the value of a key
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
bool well_formed = true;
|
||||||
|
std::size_t max_errors;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// a value is passed: it is an array element, or the value of a key
|
||||||
|
void value()
|
||||||
|
{
|
||||||
|
++events;
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
else if (stack.back() == 'o')
|
||||||
|
{
|
||||||
|
// a value without a key
|
||||||
|
well_formed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RecoveryResult
|
||||||
|
{
|
||||||
|
json value;
|
||||||
|
std::vector<std::string> errors;
|
||||||
|
std::size_t events;
|
||||||
|
bool ok;
|
||||||
|
bool balanced;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename InputType>
|
||||||
|
RecoveryResult parse_recovering(InputType&& input, const bool strict = true,
|
||||||
|
const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j);
|
||||||
|
const bool ok = json::sax_parse(std::forward<InputType>(input), &sax, json::input_format_t::json,
|
||||||
|
strict, ignore_comments, ignore_trailing_commas);
|
||||||
|
return {j, sax.errors, sax.events, ok, sax.balanced()};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// stops after a number of events, but recovers from errors
|
||||||
|
class RecoveringCountdown
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RecoveringCountdown(const int count)
|
||||||
|
: countdown(count)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null()
|
||||||
|
{
|
||||||
|
return countdown.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val)
|
||||||
|
{
|
||||||
|
return countdown.boolean(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(json::number_integer_t val)
|
||||||
|
{
|
||||||
|
return countdown.number_integer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(json::number_unsigned_t val)
|
||||||
|
{
|
||||||
|
return countdown.number_unsigned(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(json::number_float_t val, const std::string& s)
|
||||||
|
{
|
||||||
|
return countdown.number_float(val, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val)
|
||||||
|
{
|
||||||
|
return countdown.string(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(json::binary_t& val)
|
||||||
|
{
|
||||||
|
return countdown.binary(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements)
|
||||||
|
{
|
||||||
|
return countdown.start_object(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val)
|
||||||
|
{
|
||||||
|
return countdown.key(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object()
|
||||||
|
{
|
||||||
|
return countdown.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements)
|
||||||
|
{
|
||||||
|
return countdown.start_array(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array()
|
||||||
|
{
|
||||||
|
return countdown.end_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) // NOLINT(readability-convert-member-functions-to-static)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SaxCountdown countdown;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// a repaired input: the value it is repaired to, and the number of errors
|
||||||
|
struct Repair
|
||||||
|
{
|
||||||
|
const char* input;
|
||||||
|
const char* expected;
|
||||||
|
std::size_t errors;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("parser error recovery (#3989)")
|
||||||
|
{
|
||||||
|
SECTION("repairs")
|
||||||
|
{
|
||||||
|
const std::vector<Repair> repairs =
|
||||||
|
{
|
||||||
|
// a missing separator is inserted
|
||||||
|
{"[1 2]", "[1,2]", 1},
|
||||||
|
{R"({"a":1 "b":2})", R"({"a":1,"b":2})", 1},
|
||||||
|
{R"({"a" 1})", R"({"a":1})", 1},
|
||||||
|
{"[1 tru 2]", "[1,null,2]", 2},
|
||||||
|
{R"({"a" "b": 1})", R"({"a":"b"})", 2},
|
||||||
|
|
||||||
|
// a missing value is null in an object; in an array, a ',' stands
|
||||||
|
// for null, while an array that ends there just ends
|
||||||
|
{R"({"a":})", R"({"a":null})", 1},
|
||||||
|
{R"({"a"})", R"({"a":null})", 1},
|
||||||
|
{R"({"a","b":1})", R"({"a":null,"b":1})", 1},
|
||||||
|
{"[1,,2]", "[1,null,2]", 1},
|
||||||
|
{"[,1]", "[null,1]", 1},
|
||||||
|
{"[1,]", "[1]", 1},
|
||||||
|
{"[1,2,3,]", "[1,2,3]", 1},
|
||||||
|
{R"({"a":1,})", R"({"a":1})", 1},
|
||||||
|
|
||||||
|
// a broken string keeps what can be read
|
||||||
|
{R"(["a\qb"])", R"(["aqb"])", 1},
|
||||||
|
{R"({"na\me":1})", R"({"name":1})", 1},
|
||||||
|
{"[\"\xFF\"]", R"(["\uFFFD"])", 1},
|
||||||
|
{"[\"a\xC3(\"]", R"(["a\uFFFD("])", 1},
|
||||||
|
{"[\"\xE2\x82\"]", R"(["\uFFFD"])", 1},
|
||||||
|
{"[\"\xC3\\\\\", 1]", R"(["\uFFFD\\",1])", 1},
|
||||||
|
{R"(["\u12"])", R"(["\uFFFD"])", 1},
|
||||||
|
{R"(["\u12G4"])", R"(["\uFFFDG4"])", 1},
|
||||||
|
{R"(["\uDC00x"])", R"(["\uFFFDx"])", 1},
|
||||||
|
{R"(["\uD800x"])", R"(["\uFFFDx"])", 1},
|
||||||
|
{R"(["\uD800\u0041"])", R"(["\uFFFDA"])", 1},
|
||||||
|
{R"(["\uD800\uD800\uDC00"])", R"(["\uFFFD\uD800\uDC00"])", 1},
|
||||||
|
{R"(["\uD800\uD800\uD800x"])", R"(["\uFFFD\uFFFD\uFFFDx"])", 1},
|
||||||
|
{
|
||||||
|
R"(["\uD800\"x", 1])", R"(["\uFFFD\"x",1])", 1
|
||||||
|
},
|
||||||
|
{R"(["\uD800\q"])", R"(["\uFFFDq"])", 1},
|
||||||
|
{"[\"a\tb\"]", R"(["a\tb"])", 1},
|
||||||
|
{R"(["a\qb\u0041\x"])", R"(["aqbAx"])", 1},
|
||||||
|
|
||||||
|
// a broken number keeps its longest valid prefix
|
||||||
|
{"[1.]", "[1]", 1},
|
||||||
|
{"[-2.]", "[-2]", 1},
|
||||||
|
{"[1.5e]", "[1.5]", 1},
|
||||||
|
{"[1e+]", "[1]", 1},
|
||||||
|
{"[1.x2, 3]", "[1,3]", 1},
|
||||||
|
|
||||||
|
// what cannot be read at all is null
|
||||||
|
{"[1,NaN,3]", "[1,null,3]", 1},
|
||||||
|
{"[tru]", "[null]", 1},
|
||||||
|
{"[-]", "[null]", 1},
|
||||||
|
{R"({"a":Infinity})", R"({"a":null})", 1},
|
||||||
|
|
||||||
|
// a stray token is dropped
|
||||||
|
{"[:1]", "[1]", 1},
|
||||||
|
{R"(["a":1])", R"(["a",1])", 1},
|
||||||
|
{R"({"a"::1})", R"({"a":1})", 1},
|
||||||
|
|
||||||
|
// a member that cannot be read is skipped
|
||||||
|
{R"({1:2,"b":3})", R"({"b":3})", 1},
|
||||||
|
{R"({"a":1 2})", R"({"a":1})", 1},
|
||||||
|
{R"({,"a":1})", R"({"a":1})", 1},
|
||||||
|
{R"({"a":1,,"b":2})", R"({"a":1,"b":2})", 1},
|
||||||
|
{"{a:1}", "{}", 1},
|
||||||
|
{R"({"a":1 [1,{"b":2}], "c":3})", R"({"a":1,"c":3})", 1},
|
||||||
|
{R"([{1}, "a"])", R"([{},"a"])", 1},
|
||||||
|
|
||||||
|
// a wrong closing bracket closes the innermost container
|
||||||
|
{R"({"a":[1,2}, "b":3})", R"({"a":[1,2],"b":3})", 1},
|
||||||
|
{R"([{"a":1], 2])", R"([{"a":1},2])", 1},
|
||||||
|
{"{]", "{}", 1},
|
||||||
|
{"[}", "[]", 1},
|
||||||
|
|
||||||
|
// the end of the input closes all containers
|
||||||
|
{R"({"a":[1,2)", R"({"a":[1,2]})", 1},
|
||||||
|
{"[", "[]", 1},
|
||||||
|
{"{", "{}", 1},
|
||||||
|
{R"({"a")", R"({"a":null})", 1},
|
||||||
|
{R"({"a":)", R"({"a":null})", 1},
|
||||||
|
{"[1,", "[1]", 1},
|
||||||
|
{"[[[1", "[[[1]]]", 1},
|
||||||
|
{
|
||||||
|
R"(["abc)", R"(["abc"])", 2
|
||||||
|
},
|
||||||
|
{"[1,tr", "[1,null]", 2},
|
||||||
|
{"\"abc", "\"abc\"", 1},
|
||||||
|
{"[\"ab\ncd\"]", R"(["ab",null,"]"])", 4},
|
||||||
|
|
||||||
|
// what comes before the top-level value is skipped
|
||||||
|
{")]}'\n{\"a\":1}", R"({"a":1})", 1},
|
||||||
|
{R"(data: {"a":1})", R"({"a":1})", 1},
|
||||||
|
{"\xEF\xBB[1]", "[1]", 1},
|
||||||
|
|
||||||
|
// what comes after it is an error that ends parsing
|
||||||
|
{R"({"a":1}})", R"({"a":1})", 1},
|
||||||
|
{"[1}]", "[1]", 2},
|
||||||
|
{"[1] [2]", "[1]", 1},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& repair : repairs)
|
||||||
|
{
|
||||||
|
CAPTURE(repair.input)
|
||||||
|
const auto result = parse_recovering(std::string(repair.input));
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse(repair.expected));
|
||||||
|
CHECK(result.errors.size() == repair.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("number overflow")
|
||||||
|
{
|
||||||
|
const auto result = parse_recovering(std::string("[1e999,-1e999]"));
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors.size() == 2);
|
||||||
|
CHECK(result.errors[0] == "[json.exception.out_of_range.406] number overflow parsing '1e999'");
|
||||||
|
REQUIRE(result.value.size() == 2);
|
||||||
|
CHECK(result.value[0].is_number_float());
|
||||||
|
CHECK(result.value[0].get<double>() == std::numeric_limits<double>::infinity());
|
||||||
|
CHECK(result.value[1].get<double>() == -std::numeric_limits<double>::infinity());
|
||||||
|
|
||||||
|
// the SAX parser gets the number's text
|
||||||
|
SaxEventLogger logger;
|
||||||
|
logger.recover = true;
|
||||||
|
CHECK(!json::sax_parse("1e999", &logger));
|
||||||
|
CHECK(logger.events == std::vector<std::string>({"parse_error(5)", "number_float(1e999)"}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("nothing to recover")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"", " ", "]", "tru", "NaN", ",:", "/* comment"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const auto result = parse_recovering(s, true, true);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.events == 0);
|
||||||
|
CHECK(result.value == nullptr);
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error messages")
|
||||||
|
{
|
||||||
|
// the first error is reported as without recovery
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1 2]", R"({"a":1 "b":2})", R"({"a" 1})", R"({"a":})", "[1,]", "[1.]",
|
||||||
|
R"(["a\qb"])", "[1e999]", "{1:2}", R"({"a":[1,2}})", "[1,", "[1] [2]", "{a:1}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const auto result = parse_recovering(s);
|
||||||
|
REQUIRE(!result.errors.empty());
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_STD_STR(_ = json::parse(s), result.errors.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
// the token of an error begins where the previous error was
|
||||||
|
const auto result = parse_recovering(std::string("[tru, fals, nul]"));
|
||||||
|
CHECK(result.errors == std::vector<std::string>(
|
||||||
|
{
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: '[tru,'",
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 11: syntax error while parsing value - invalid literal; last read: ', fals,'",
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 16: syntax error while parsing value - invalid literal; last read: ', nul]'"
|
||||||
|
}));
|
||||||
|
CHECK(result.value == json::parse("[null,null,null]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("events")
|
||||||
|
{
|
||||||
|
// see #4522
|
||||||
|
SaxEventLogger logger;
|
||||||
|
logger.recover = true;
|
||||||
|
CHECK(!json::sax_parse(R"([{1}, "a"])", &logger));
|
||||||
|
CHECK(logger.events == std::vector<std::string>(
|
||||||
|
{
|
||||||
|
"start_array()", "start_object()", "parse_error(3)", "end_object()", "string(a)", "end_array()"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("options")
|
||||||
|
{
|
||||||
|
SECTION("strict")
|
||||||
|
{
|
||||||
|
const auto result = parse_recovering(std::string("[1 2] [3]"), false);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignore_trailing_commas")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1,]", R"({"a":1,})", "[[1,],]"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const auto result = parse_recovering(s, true, false, true);
|
||||||
|
CHECK(result.ok);
|
||||||
|
CHECK(result.errors.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = parse_recovering(std::string("[1,,]"), true, false, true);
|
||||||
|
CHECK(result.value == json::parse("[1,null]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
result = parse_recovering(std::string(R"({"a":1,,})"), true, false, true);
|
||||||
|
CHECK(result.value == json::parse(R"({"a":1})"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ignore_comments")
|
||||||
|
{
|
||||||
|
auto result = parse_recovering(std::string("[1 /* one */ 2]"), true, true);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
// a comment that is not closed runs to the end of the input, which
|
||||||
|
// is not reported again
|
||||||
|
result = parse_recovering(std::string("[1, 2 /* unterminated"), true, true);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse("[1,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
|
||||||
|
// a '/' that does not begin a comment is garbage
|
||||||
|
result = parse_recovering(std::string("[1, /x, 2]"), true, true);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json::parse("[1,null,2]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("null bytes")
|
||||||
|
{
|
||||||
|
// a null byte ends the input, unless JSON_STRICT_NUL_HANDLING is set
|
||||||
|
const auto result = parse_recovering(std::string("[1,\0x", 5));
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||||
|
CHECK(result.value == json::parse("[1,null]"));
|
||||||
|
#else
|
||||||
|
CHECK(result.value == json::parse("[1]"));
|
||||||
|
CHECK(result.errors.size() == 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const auto in_string = parse_recovering(std::string("[\"a\0b\"]", 7));
|
||||||
|
CHECK(in_string.balanced);
|
||||||
|
#ifdef JSON_TEST_STRICT_NUL_HANDLING_ENABLED
|
||||||
|
CHECK(in_string.value == json::array({std::string("a\0b", 3)}));
|
||||||
|
#else
|
||||||
|
CHECK(in_string.value == json::parse(R"(["a"])"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the SAX parser stops recovering")
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j, 2);
|
||||||
|
CHECK(!json::sax_parse("[1 2 3 4 5]", &sax));
|
||||||
|
CHECK(sax.errors.size() == 2);
|
||||||
|
|
||||||
|
// an error at a delimiter that an invalid token consumed is reported
|
||||||
|
// to the SAX parser, too
|
||||||
|
json j2;
|
||||||
|
RecoveringDomParser sax2(j2, 2);
|
||||||
|
CHECK(!json::sax_parse("[tru}, 1]", &sax2));
|
||||||
|
CHECK(sax2.errors.size() == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an event stops parsing during a repair")
|
||||||
|
{
|
||||||
|
// start_object() and key() are passed, then null() for the missing
|
||||||
|
// value returns false
|
||||||
|
RecoveringCountdown countdown(2);
|
||||||
|
CHECK(!json::sax_parse(R"({"a":})", &countdown));
|
||||||
|
|
||||||
|
// the end of the input: end_array() for the second array returns false
|
||||||
|
RecoveringCountdown countdown2(4);
|
||||||
|
CHECK(!json::sax_parse("[[1", &countdown2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("input adapters")
|
||||||
|
{
|
||||||
|
// the lexer reads contiguous and streaming input differently, and it
|
||||||
|
// puts back a character that ended an invalid token
|
||||||
|
for (const std::string s :
|
||||||
|
{
|
||||||
|
"[1 2]", "[tru}, 1]", R"({"a" "b\q", "c":[1.x, 2}})", "[\"\xFF\xC3(\", -, 1e+]", "{a:1,\"b\":2", ")]}' [1]"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const auto reference = parse_recovering(s);
|
||||||
|
CHECK(reference.balanced);
|
||||||
|
|
||||||
|
const auto from_c_string = parse_recovering(s.c_str());
|
||||||
|
CHECK(from_c_string.value == reference.value);
|
||||||
|
CHECK(from_c_string.errors == reference.errors);
|
||||||
|
|
||||||
|
const std::list<char> l(s.begin(), s.end());
|
||||||
|
json j;
|
||||||
|
RecoveringDomParser sax(j);
|
||||||
|
CHECK(!json::sax_parse(l.begin(), l.end(), &sax));
|
||||||
|
CHECK(j == reference.value);
|
||||||
|
CHECK(sax.errors == reference.errors);
|
||||||
|
|
||||||
|
std::istringstream ss(s);
|
||||||
|
const auto from_stream = parse_recovering(ss);
|
||||||
|
CHECK(from_stream.value == reference.value);
|
||||||
|
CHECK(from_stream.errors == reference.errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("long runs of errors")
|
||||||
|
{
|
||||||
|
// no error may copy all the input read before it
|
||||||
|
const auto closing = parse_recovering("[" + std::string(100000, '}'));
|
||||||
|
CHECK(closing.balanced);
|
||||||
|
CHECK(closing.value == json::array());
|
||||||
|
|
||||||
|
const auto garbage = parse_recovering("[" + std::string(100000, 'x') + "]");
|
||||||
|
CHECK(garbage.balanced);
|
||||||
|
CHECK(garbage.errors.size() == 1);
|
||||||
|
|
||||||
|
const auto commas = parse_recovering("{" + std::string(100000, ',') + "}");
|
||||||
|
CHECK(commas.balanced);
|
||||||
|
CHECK(commas.value == json::object());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("mutations of valid input")
|
||||||
|
{
|
||||||
|
// whatever the input, the events are balanced, every error is reported
|
||||||
|
// at most once, and valid input is parsed as usual
|
||||||
|
const std::vector<std::string> documents =
|
||||||
|
{
|
||||||
|
R"({"name": "value", "list": [1, -2.5, true, null, {"x": [[]]}], "e": "\u00e9"})",
|
||||||
|
R"([{"a": [1, 2, {"b": "c"}]}, [], {}, "\ud83d\ude00", 1e10])",
|
||||||
|
"{\"\xC3\xA9\": \"\xF0\x9F\x98\x80\"}",
|
||||||
|
R"( {"k" : [ "v" , 0 ] } )",
|
||||||
|
};
|
||||||
|
// each character that can be inserted, including a null byte
|
||||||
|
const std::string insertions("[]{},:\"x\\\0\xFF", 11);
|
||||||
|
|
||||||
|
std::vector<std::string> inputs;
|
||||||
|
for (const auto& doc : documents)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i <= doc.size(); ++i)
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i));
|
||||||
|
if (i < doc.size())
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i) + doc.substr(i + 1));
|
||||||
|
}
|
||||||
|
for (const char c : insertions)
|
||||||
|
{
|
||||||
|
inputs.push_back(doc.substr(0, i) + c + doc.substr(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& s : inputs)
|
||||||
|
{
|
||||||
|
CAPTURE(s)
|
||||||
|
const auto result = parse_recovering(s);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors.size() <= s.size() + 1);
|
||||||
|
CHECK(result.events <= (4 * s.size()) + 4);
|
||||||
|
if (json::accept(s))
|
||||||
|
{
|
||||||
|
CHECK(result.ok);
|
||||||
|
CHECK(result.errors.empty());
|
||||||
|
CHECK(result.value == json::parse(s));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(!result.errors.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -285,6 +285,58 @@ 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);
|
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")
|
SECTION("BJData ND-array")
|
||||||
{
|
{
|
||||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||||
|
|||||||
@@ -185,6 +185,33 @@ TEST_CASE("json_view")
|
|||||||
// duplicate keys: the last value, at the position of the first key
|
// 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<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})");
|
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())
|
// very deep nesting (iterative, as parse())
|
||||||
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
const std::string deep = std::string(100000, '[') + std::string(100000, ']');
|
||||||
// (one check per step, so that a failure shows which step it is)
|
// (one check per step, so that a failure shows which step it is)
|
||||||
@@ -410,6 +437,31 @@ TEST_CASE("json_view")
|
|||||||
std::istringstream rvalue_source("[1]");
|
std::istringstream rvalue_source("[1]");
|
||||||
CHECK(json_document::parse_copy(std::move(rvalue_source)).owns_source());
|
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
|
// a stream without a buffer is an empty input
|
||||||
std::istream no_buffer(nullptr);
|
std::istream no_buffer(nullptr);
|
||||||
CHECK(json_document::parse(no_buffer, false).is_discarded());
|
CHECK(json_document::parse(no_buffer, false).is_discarded());
|
||||||
@@ -1006,6 +1058,49 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
#endif
|
#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")
|
SECTION("iterators")
|
||||||
{
|
{
|
||||||
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
|
const json_document d = json_document::parse(R"({"x":[1,{"y":2}],"z":null})");
|
||||||
@@ -1045,6 +1140,28 @@ TEST_CASE("json_view element access and iteration")
|
|||||||
CHECK((*first++).key() == "0");
|
CHECK((*first++).key() == "0");
|
||||||
CHECK((*first).key() == "1");
|
CHECK((*first).key() == "1");
|
||||||
CHECK(++first == items.end());
|
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
|
#ifdef JSON_HAS_CPP_17
|
||||||
std::string pairs;
|
std::string pairs;
|
||||||
for (const auto [key, value] : d.root().items())
|
for (const auto [key, value] : d.root().items())
|
||||||
@@ -1383,7 +1500,17 @@ 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.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("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)); })));
|
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
|
#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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -523,6 +523,58 @@ 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")
|
TEST_CASE("json_view node integer bits")
|
||||||
{
|
{
|
||||||
using nlohmann::detail::view::integer_bits;
|
using nlohmann::detail::view::integer_bits;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using nlohmann::ordered_json_view;
|
|||||||
using json_view_test::generator;
|
using json_view_test::generator;
|
||||||
using json_view_test::has_duplicate_keys;
|
using json_view_test::has_duplicate_keys;
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -29,6 +30,7 @@ using json_view_test::has_duplicate_keys;
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -186,6 +188,133 @@ TEST_CASE("json_view dump")
|
|||||||
CHECK(deep_document.root().dump() == deep);
|
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")
|
SECTION("the output buffer of a small value is small")
|
||||||
{
|
{
|
||||||
// an escaped key after the value: its node lies in the arena, so the
|
// an escaped key after the value: its node lies in the arena, so the
|
||||||
@@ -598,6 +727,152 @@ TEST_CASE("json_view large objects")
|
|||||||
CHECK(without_collisions.memory_usage() >= with_collisions.memory_usage() + (table_size * sizeof(std::uint32_t)));
|
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")
|
SECTION("shrink_to_fit releases the tables' spare capacity")
|
||||||
{
|
{
|
||||||
const auto make_text = [](int objects, int members)
|
const auto make_text = [](int objects, int members)
|
||||||
|
|||||||
@@ -661,6 +661,115 @@ TEST_CASE("json_view edits: replacing arrays and objects by scalars")
|
|||||||
CHECK(d.root().materialize() == expected);
|
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
|
namespace
|
||||||
{
|
{
|
||||||
// reads of "a" in an object that repeats it: the first member wins, however the
|
// reads of "a" in an object that repeats it: the first member wins, however the
|
||||||
|
|||||||
@@ -682,6 +682,26 @@ TEST_CASE("json_view images: ownership")
|
|||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("json_view images: errors")
|
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")
|
SECTION("a literal as the root: dump() after loading")
|
||||||
{
|
{
|
||||||
for (const char* text :
|
for (const char* text :
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ using nlohmann::json;
|
|||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "round_trip_corpus.hpp"
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
#include "sax_countdown.hpp"
|
|
||||||
using utils::SaxCountdown;
|
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("MessagePack")
|
TEST_CASE("MessagePack")
|
||||||
@@ -1645,30 +1643,6 @@ TEST_CASE("MessagePack")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("SAX aborts")
|
|
||||||
{
|
|
||||||
SECTION("start_array(len)")
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> const v = {0x93, 0x01, 0x02, 0x03};
|
|
||||||
SaxCountdown scp(0);
|
|
||||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("start_object(len)")
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
|
||||||
SaxCountdown scp(0);
|
|
||||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("key()")
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
|
||||||
SaxCountdown scp(1);
|
|
||||||
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
||||||
@@ -1739,21 +1713,6 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
|||||||
CHECK(json::from_msgpack(packed) == j);
|
CHECK(json::from_msgpack(packed) == j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a user-defined SAX consumer is unaffected by the internal DOM reserve optimization")
|
|
||||||
{
|
|
||||||
// the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser;
|
|
||||||
// a custom SAX consumer that does not touch a DOM array sees identical events
|
|
||||||
json j = json::array();
|
|
||||||
for (int i = 0; i < 100; ++i)
|
|
||||||
{
|
|
||||||
j.push_back(i);
|
|
||||||
}
|
|
||||||
const auto packed = json::to_msgpack(j);
|
|
||||||
|
|
||||||
SaxCountdown scp(1000000); // large enough to never trigger an abort
|
|
||||||
CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte")
|
TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte")
|
||||||
@@ -1792,15 +1751,6 @@ TEST_CASE("MessagePack nesting does not consume the call stack")
|
|||||||
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
CHECK(json::from_msgpack(input, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a well-formed deep value is read through the SAX interface")
|
|
||||||
{
|
|
||||||
std::vector<uint8_t> input(300000, 0x91);
|
|
||||||
input.push_back(0x01); // innermost value
|
|
||||||
|
|
||||||
SaxCountdown accept_all(600001);
|
|
||||||
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack));
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("a well-formed deep value is read into a value")
|
SECTION("a well-formed deep value is read into a value")
|
||||||
{
|
{
|
||||||
const std::size_t depth = 10000;
|
const std::size_t depth = 10000;
|
||||||
@@ -1848,31 +1798,6 @@ TEST_CASE("MessagePack input that cannot be read is discarded by every overload"
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("MessagePack SAX parsing stops at every event")
|
|
||||||
{
|
|
||||||
// Containers are opened and closed by the loop that reads them; a SAX
|
|
||||||
// handler that rejects any event - including the end of a nested
|
|
||||||
// container - must stop the parse right there.
|
|
||||||
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
|
||||||
{
|
|
||||||
int events = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
SaxCountdown scp(events);
|
|
||||||
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
|
||||||
{
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
++events;
|
|
||||||
REQUIRE(events < 1000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 20 events: every container kind closes inside another one
|
|
||||||
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
|
||||||
CHECK(count_events(json::to_msgpack(j)) == 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("single MessagePack roundtrip")
|
TEST_CASE("single MessagePack roundtrip")
|
||||||
{
|
{
|
||||||
SECTION("sample.json")
|
SECTION("sample.json")
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
using nlohmann::ordered_json;
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
TEST_CASE("ordered_json")
|
TEST_CASE("ordered_json")
|
||||||
{
|
{
|
||||||
json j;
|
json j;
|
||||||
@@ -237,3 +242,276 @@ TEST_CASE("regression test for issue #5673 - ordered_json::emplace with a non-rv
|
|||||||
CHECK(oj.dump() == "{\"a\":1}");
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,14 +22,6 @@
|
|||||||
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
// scoped enum, so get<std::byte>() (needed below to get<std::vector<std::byte>>()
|
||||||
// from a plain JSON array, not just from an already-binary value) relies on
|
// from a plain JSON array, not just from an already-binary value) relies on
|
||||||
// enum serialization being enabled
|
// enum serialization being enabled
|
||||||
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
|
||||||
// line *before* including json.hpp, since the library #undefs it once the header
|
|
||||||
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
|
||||||
// tests of deprecated functions are skipped if these functions are deleted
|
|
||||||
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
|
||||||
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
||||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
||||||
#endif
|
#endif
|
||||||
@@ -810,50 +802,6 @@ TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("issue #5676 - SAX parsing of CBOR tags")
|
|
||||||
{
|
|
||||||
const json expected = json::binary({1, 2, 3}, 42);
|
|
||||||
const auto cbor = json::to_cbor(expected);
|
|
||||||
|
|
||||||
nlohmann::detail::json_sax_acceptor<json> acceptor;
|
|
||||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor));
|
|
||||||
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::error));
|
|
||||||
|
|
||||||
CHECK(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::ignore));
|
|
||||||
|
|
||||||
json parsed;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(parsed);
|
|
||||||
CHECK(json::sax_parse(cbor, &sax, json::input_format_t::cbor,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
CHECK(parsed == expected);
|
|
||||||
|
|
||||||
json iterator_parsed;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> iterator_sax(iterator_parsed);
|
|
||||||
CHECK(json::sax_parse(cbor.begin(), cbor.end(), &iterator_sax, json::input_format_t::cbor,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
CHECK(iterator_parsed == expected);
|
|
||||||
|
|
||||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
|
||||||
json span_parsed;
|
|
||||||
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> span_sax(span_parsed);
|
|
||||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax,
|
|
||||||
json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
CHECK(span_parsed == expected);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const std::string text = "null";
|
|
||||||
CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json,
|
|
||||||
true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
|
||||||
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor,
|
|
||||||
json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
TEST_CASE("issue #5402 - update(merge_objects=true) overwrites a primitive with an object")
|
||||||
{
|
{
|
||||||
json t = {{"k", 1}};
|
json t = {{"k", 1}};
|
||||||
|
|||||||
@@ -0,0 +1,768 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// Tests that call basic_json::sax_parse have a file of their own: every
|
||||||
|
// sax_parse call instantiates the parser and binary reader that recover from
|
||||||
|
// errors (see #3989), and in unit-regression2.cpp, unit-regression3.cpp, and
|
||||||
|
// unit-msgpack.cpp this made the objects too large for the MinGW linker to
|
||||||
|
// relocate (see #5511).
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
|
// capture whether JSON_DELETE_DEPRECATED_FUNCTIONS was enabled on the command
|
||||||
|
// line *before* including json.hpp, since the library #undefs it once the header
|
||||||
|
// has been fully processed (see include/nlohmann/detail/macro_unscope.hpp); the
|
||||||
|
// tests of deprecated functions are skipped if these functions are deleted
|
||||||
|
#if defined(JSON_DELETE_DEPRECATED_FUNCTIONS) && (JSON_DELETE_DEPRECATED_FUNCTIONS == 1)
|
||||||
|
#define JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sax_countdown.hpp"
|
||||||
|
using utils::SaxCountdown;
|
||||||
|
|
||||||
|
// a narrow number_float_t, so that a double read from binary input can
|
||||||
|
// overflow it
|
||||||
|
using float_json = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t, float>;
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
/// builds a value from SAX events, asks the parser to recover from its first
|
||||||
|
/// 100 errors, and checks that the events are balanced (see #3989)
|
||||||
|
template<typename BasicJsonType>
|
||||||
|
class BasicRecoveringParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BasicRecoveringParser(BasicJsonType& j)
|
||||||
|
: dom(j, false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool null()
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.null();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool boolean(bool val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.boolean(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_integer(typename BasicJsonType::number_integer_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_integer(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_unsigned(typename BasicJsonType::number_unsigned_t val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_unsigned(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool number_float(typename BasicJsonType::number_float_t val, const std::string& s)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.number_float(val, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string(std::string& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.string(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool binary(typename BasicJsonType::binary_t& val)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
return dom.binary(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_object(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('o');
|
||||||
|
return dom.start_object(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool key(std::string& val)
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.back() = 'v';
|
||||||
|
return dom.key(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_object()
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_object();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start_array(std::size_t elements)
|
||||||
|
{
|
||||||
|
value();
|
||||||
|
stack.push_back('a');
|
||||||
|
return dom.start_array(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool end_array()
|
||||||
|
{
|
||||||
|
if (stack.empty() || stack.back() != 'a')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
stack.pop_back();
|
||||||
|
return dom.end_array();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const json::exception& ex)
|
||||||
|
{
|
||||||
|
messages.emplace_back(ex.what());
|
||||||
|
// a limit, so that a reader that does not stop fails the test
|
||||||
|
// instead of making it hang
|
||||||
|
return ++errors < 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the events were balanced and every key was followed by a value
|
||||||
|
bool balanced() const
|
||||||
|
{
|
||||||
|
return well_formed && stack.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// builds the value
|
||||||
|
nlohmann::detail::json_sax_dom_parser<BasicJsonType> dom;
|
||||||
|
std::size_t errors = 0;
|
||||||
|
std::vector<std::string> messages {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
std::vector<char> stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
bool well_formed = true;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void value()
|
||||||
|
{
|
||||||
|
if (!stack.empty())
|
||||||
|
{
|
||||||
|
if (stack.back() == 'v')
|
||||||
|
{
|
||||||
|
stack.back() = 'o';
|
||||||
|
}
|
||||||
|
else if (stack.back() == 'o')
|
||||||
|
{
|
||||||
|
well_formed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
using RecoveringParser = BasicRecoveringParser<json>;
|
||||||
|
|
||||||
|
struct BinaryParseResult
|
||||||
|
{
|
||||||
|
json value;
|
||||||
|
std::size_t errors;
|
||||||
|
std::vector<std::string> messages;
|
||||||
|
bool ok;
|
||||||
|
bool balanced;
|
||||||
|
};
|
||||||
|
|
||||||
|
BinaryParseResult parse_binary_recovering(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
RecoveringParser sax(j);
|
||||||
|
const bool ok = json::sax_parse(input, &sax, format);
|
||||||
|
return {j, sax.errors, sax.messages, ok, sax.balanced()};
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
/// the message of the exception that reading @a input into a JSON value
|
||||||
|
/// throws, or an empty string if reading succeeds
|
||||||
|
std::string binary_error_message(const std::vector<std::uint8_t>& input, const json::input_format_t format)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case json::input_format_t::cbor:
|
||||||
|
_ = json::from_cbor(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::msgpack:
|
||||||
|
_ = json::from_msgpack(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::ubjson:
|
||||||
|
_ = json::from_ubjson(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bjdata:
|
||||||
|
_ = json::from_bjdata(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bson:
|
||||||
|
_ = json::from_bson(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::bon8:
|
||||||
|
_ = json::from_bon8(input);
|
||||||
|
break;
|
||||||
|
case json::input_format_t::json:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// a BSON element: its type, its name, and its value
|
||||||
|
std::vector<std::uint8_t> bson_element(const std::uint8_t type, const std::string& name, const std::vector<std::uint8_t>& value)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> result = {type};
|
||||||
|
result.insert(result.end(), name.begin(), name.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
result.insert(result.end(), value.begin(), value.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON document of the given elements; @a size_offset is added to the
|
||||||
|
/// size it declares
|
||||||
|
std::vector<std::uint8_t> bson_document(const std::vector<std::vector<std::uint8_t>>& elements, const int size_offset = 0)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> body;
|
||||||
|
for (const auto& element : elements)
|
||||||
|
{
|
||||||
|
body.insert(body.end(), element.begin(), element.end());
|
||||||
|
}
|
||||||
|
const auto size = static_cast<std::uint32_t>(static_cast<int>(body.size()) + 5 + size_offset);
|
||||||
|
std::vector<std::uint8_t> result = {static_cast<std::uint8_t>(size & 0xFFu), static_cast<std::uint8_t>((size >> 8u) & 0xFFu),
|
||||||
|
static_cast<std::uint8_t>((size >> 16u) & 0xFFu), static_cast<std::uint8_t>((size >> 24u) & 0xFFu)
|
||||||
|
};
|
||||||
|
result.insert(result.end(), body.begin(), body.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON int32 value
|
||||||
|
std::vector<std::uint8_t> bson_int32(const std::int32_t value)
|
||||||
|
{
|
||||||
|
const auto u = static_cast<std::uint32_t>(value);
|
||||||
|
return {static_cast<std::uint8_t>(u & 0xFFu), static_cast<std::uint8_t>((u >> 8u) & 0xFFu),
|
||||||
|
static_cast<std::uint8_t>((u >> 16u) & 0xFFu), static_cast<std::uint8_t>((u >> 24u) & 0xFFu)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a BSON string value, whose length is @a length_offset off
|
||||||
|
std::vector<std::uint8_t> bson_string(const std::string& value, const std::int32_t length_offset = 0)
|
||||||
|
{
|
||||||
|
auto result = bson_int32(static_cast<std::int32_t>(value.size() + 1) + length_offset);
|
||||||
|
result.insert(result.end(), value.begin(), value.end());
|
||||||
|
result.push_back(0x00);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @a count bytes of value 0xAB
|
||||||
|
std::vector<std::uint8_t> bytes(const std::size_t count)
|
||||||
|
{
|
||||||
|
return std::vector<std::uint8_t>(count, 0xAB);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the bytes of @a parts, one after the other
|
||||||
|
std::vector<std::uint8_t> concatenated(std::initializer_list<std::vector<std::uint8_t>> parts)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> result;
|
||||||
|
for (const auto& part : parts)
|
||||||
|
{
|
||||||
|
result.insert(result.end(), part.begin(), part.end());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// U+FFFD REPLACEMENT CHARACTER
|
||||||
|
std::string replacement_character()
|
||||||
|
{
|
||||||
|
return "\xEF\xBF\xBD";
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("regression test - #3989 SAX parse_error() returning true")
|
||||||
|
{
|
||||||
|
SECTION("binary formats complete what was read before the input ends")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}};
|
||||||
|
|
||||||
|
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||||
|
{
|
||||||
|
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||||
|
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||||
|
{json::input_format_t::bson, json::to_bson(j)},
|
||||||
|
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& encoding : encodings)
|
||||||
|
{
|
||||||
|
const auto format = encoding.first;
|
||||||
|
const auto& bytes = encoding.second;
|
||||||
|
CAPTURE(format)
|
||||||
|
|
||||||
|
// every prefix is truncated input
|
||||||
|
for (std::size_t length = 0; length < bytes.size(); ++length)
|
||||||
|
{
|
||||||
|
CAPTURE(length)
|
||||||
|
const auto result = parse_binary_recovering(std::vector<std::uint8_t>(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length)), format);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.errors == 1);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the complete input is read as usual (binary values do not
|
||||||
|
// round-trip through every format, so compare with a plain parse)
|
||||||
|
json expected;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json> dom(expected);
|
||||||
|
CHECK(json::sax_parse(bytes, &dom, format));
|
||||||
|
const auto complete = parse_binary_recovering(bytes, format);
|
||||||
|
CHECK(complete.ok);
|
||||||
|
CHECK(complete.errors == 0);
|
||||||
|
CHECK(complete.value == expected);
|
||||||
|
|
||||||
|
// a byte after the value
|
||||||
|
auto trailing_bytes = bytes;
|
||||||
|
trailing_bytes.push_back(0x01);
|
||||||
|
const auto trailing = parse_binary_recovering(trailing_bytes, format);
|
||||||
|
CHECK(!trailing.ok);
|
||||||
|
CHECK(trailing.errors == 1);
|
||||||
|
CHECK(trailing.value == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("containers without an end")
|
||||||
|
{
|
||||||
|
// these made the readers loop, or read on, after the error
|
||||||
|
const auto cbor_array = parse_binary_recovering({0x9F}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_array.errors == 1);
|
||||||
|
CHECK(cbor_array.value == json::array());
|
||||||
|
|
||||||
|
const auto cbor_map = parse_binary_recovering({0xBF, 0x61, 'a'}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_map.errors == 1);
|
||||||
|
CHECK(cbor_map.value == json({{"a", nullptr}}));
|
||||||
|
|
||||||
|
const auto msgpack_array = parse_binary_recovering({0xDD, 0xFF, 0xFF, 0xFF, 0xFF}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack_array.errors == 1);
|
||||||
|
CHECK(msgpack_array.value == json::array());
|
||||||
|
|
||||||
|
const auto msgpack_map = parse_binary_recovering({0x81, 0xA1, 'a', 0x92, 0x01}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack_map.errors == 1);
|
||||||
|
CHECK(msgpack_map.value == json({{"a", {1}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("BJData ndarray")
|
||||||
|
{
|
||||||
|
// a 2x3 int8 array with two of its six elements; the annotated array
|
||||||
|
// format opens an object and two arrays of its own
|
||||||
|
const auto result = parse_binary_recovering({'[', '$', 'i', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2}, json::input_format_t::bjdata);
|
||||||
|
CHECK(result.errors == 1);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.value == json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats repair items whose end is known")
|
||||||
|
{
|
||||||
|
struct Repair
|
||||||
|
{
|
||||||
|
json::input_format_t format;
|
||||||
|
std::vector<std::uint8_t> input;
|
||||||
|
json expected;
|
||||||
|
std::size_t errors;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<Repair> repairs =
|
||||||
|
{
|
||||||
|
// CBOR: tags are ignored (here tag 1 and the self-describe tag 55799)
|
||||||
|
{json::input_format_t::cbor, {0x82, 0xC1, 0x05, 0xD9, 0xD9, 0xF7, 0x06}, {5, 6}, 2},
|
||||||
|
// CBOR: undefined and other simple values become null
|
||||||
|
{json::input_format_t::cbor, {0x84, 0xF7, 0xE0, 0xF8, 0x20, 0x01}, {nullptr, nullptr, nullptr, 1}, 3},
|
||||||
|
// CBOR: members whose key is not a string are skipped, whatever their key and value
|
||||||
|
{json::input_format_t::cbor, {0xA4, 0x01, 0x02, 0x82, 0x01, 0x02, 0xA1, 0x61, 'x', 0x9F, 0xFF, 0xC1, 0x01, 0x5F, 0x41, 0x00, 0xFF, 0x61, 'a', 0x03}, {{"a", 3}}, 3},
|
||||||
|
{json::input_format_t::cbor, {0xBF, 0xF5, 0xBF, 0x61, 'x', 0x7F, 0x61, 'y', 0xFF, 0xFF, 0x61, 'a', 0x03, 0xFF}, {{"a", 3}}, 1},
|
||||||
|
// MessagePack: members whose key is not a string are skipped
|
||||||
|
{json::input_format_t::msgpack, {0x84, 0x01, 0x02, 0x81, 0xA1, 'x', 0x01, 0x92, 0x01, 0x02, 0xD4, 0x01, 0x02, 0xC0, 0xA1, 'a', 0x04}, {{"a", 4}}, 3},
|
||||||
|
// UBJSON: a char that is not ASCII becomes U+FFFD
|
||||||
|
{json::input_format_t::ubjson, {'[', 'C', 0x80, 'C', 'A', ']'}, {replacement_character(), "A"}, 1},
|
||||||
|
// UBJSON: the longest beginning of a high-precision number is kept
|
||||||
|
{json::input_format_t::ubjson, {'[', 'H', 'i', 5, '1', '2', 'a', 'b', 'c', 'H', 'i', 2, '1', '.', 'H', 'i', 3, 'a', 'b', 'c', 'H', 'i', 3, '4', '.', '5', ']'}, {12, 1, nullptr, 4.5}, 3},
|
||||||
|
// BJData, too
|
||||||
|
{json::input_format_t::bjdata, {'[', 'C', 0xFF, 'H', 'i', 2, '-', '1', 'H', 'i', 2, '-', 'x', ']'}, {replacement_character(), -1, nullptr}, 2},
|
||||||
|
// a NUL ends a high-precision number, as it ends JSON text
|
||||||
|
{json::input_format_t::ubjson, {'[', 'H', 'i', 4, '1', '2', 0, '9', 'H', 'i', 2, 0, '1', 'i', 3, ']'}, {12, nullptr, 3}, 2},
|
||||||
|
// BON8: members whose key is not a string are skipped
|
||||||
|
{json::input_format_t::bon8, {0x89, 0x91, 0x92, 0xC9, 0x40, 0x82, 0x91, 0x92, 0x61, 0x93}, {{"a", 3}}, 2},
|
||||||
|
{json::input_format_t::bon8, {0x8B, 0x91, 0x85, 0x91, 0xFE, 0xFA, 0x8B, 'x', 0x91, 0xFE, 0x61, 0x93, 0xFE}, {{"a", 3}}, 2},
|
||||||
|
// BSON: elements of types the library does not read become null
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x07, "_id", bytes(12)), // ObjectId
|
||||||
|
bson_element(0x09, "date", bytes(8)), // UTC datetime
|
||||||
|
bson_element(0x13, "decimal", bytes(16)), // 128-bit decimal
|
||||||
|
bson_element(0x0B, "regex", {'a', '+', 0, 'i', 0}), // regular expression
|
||||||
|
bson_element(0x0D, "code", bson_string("f()")), // JavaScript code
|
||||||
|
bson_element(0x0E, "symbol", bson_string("s")), // symbol
|
||||||
|
bson_element(0x0C, "pointer", concatenated({bson_string("c"), bytes(12)})), // DBPointer
|
||||||
|
bson_element(0x0F, "scope", concatenated({bson_int32(15), bson_string("g"), bson_document({})})), // code with scope
|
||||||
|
bson_element(0x06, "undefined", {}), // undefined
|
||||||
|
bson_element(0xFF, "min", {}), // min key
|
||||||
|
bson_element(0x7F, "max", {}), // max key
|
||||||
|
bson_element(0x10, "z", bson_int32(7)),
|
||||||
|
}),
|
||||||
|
{{"_id", nullptr}, {"date", nullptr}, {"decimal", nullptr}, {"regex", nullptr}, {"code", nullptr}, {"symbol", nullptr}, {"pointer", nullptr}, {"scope", nullptr}, {"undefined", nullptr}, {"min", nullptr}, {"max", nullptr}, {"z", 7}},
|
||||||
|
11
|
||||||
|
},
|
||||||
|
// BSON: an element of an unknown type becomes null, and the rest of its document is skipped
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3)), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x04, "array", bson_document({bson_element(0x10, "0", bson_int32(1)), bson_element(0x42, "1", bytes(3))})),
|
||||||
|
bson_element(0x10, "after", bson_int32(3)),
|
||||||
|
}),
|
||||||
|
{{"inner", {{"a", 1}, {"x", nullptr}}}, {"array", {1, nullptr}}, {"after", 3}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
// BSON: so does a string or byte array whose length cannot be right
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x02, "s", bson_string("abc", -10)), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x03, "bin", bson_document({bson_element(0x05, "b", concatenated({bson_int32(-1), bytes(1)})), bson_element(0x10, "b", bson_int32(2))})),
|
||||||
|
bson_element(0x10, "after", bson_int32(3)),
|
||||||
|
}),
|
||||||
|
{{"inner", {{"s", nullptr}}}, {"bin", {{"b", nullptr}}}, {"after", 3}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
// BSON: a string without its terminator, and a document whose size does not match, are kept
|
||||||
|
{
|
||||||
|
json::input_format_t::bson, bson_document(
|
||||||
|
{
|
||||||
|
bson_element(0x02, "s", {2, 0, 0, 0, 'a', 'X'}),
|
||||||
|
bson_element(0x03, "inner", bson_document({bson_element(0x10, "a", bson_int32(1))}, 1)),
|
||||||
|
}),
|
||||||
|
{{"s", "a"}, {"inner", {{"a", 1}}}},
|
||||||
|
2
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& repair : repairs)
|
||||||
|
{
|
||||||
|
CAPTURE(repair.format)
|
||||||
|
CAPTURE(repair.input)
|
||||||
|
const auto result = parse_binary_recovering(repair.input, repair.format);
|
||||||
|
CHECK(!result.ok);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors == repair.errors);
|
||||||
|
CHECK(result.value == repair.expected);
|
||||||
|
REQUIRE(!result.messages.empty());
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// the first error is the one reported without recovering; under
|
||||||
|
// JSON_NOEXCEPTION, reading without recovering aborts instead of
|
||||||
|
// throwing, so there is no message to compare with
|
||||||
|
CHECK(result.messages.front() == binary_error_message(repair.input, repair.format));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats repair numbers that are out of range")
|
||||||
|
{
|
||||||
|
// CBOR: a double too large for a float number_float_t
|
||||||
|
float_json cbor;
|
||||||
|
BasicRecoveringParser<float_json> sax(cbor);
|
||||||
|
const std::vector<std::uint8_t> cbor_input = {0x82, 0xFB, 0x7E, 0x37, 0xE4, 0x3C, 0x88, 0x00, 0x75, 0x9C, 0x01}; // [1e300, 1]
|
||||||
|
CHECK(!float_json::sax_parse(cbor_input, &sax, float_json::input_format_t::cbor));
|
||||||
|
CHECK(sax.errors == 1);
|
||||||
|
CHECK(sax.messages.front() == "[json.exception.out_of_range.406] syntax error while parsing CBOR value: number overflow");
|
||||||
|
REQUIRE(cbor.size() == 2);
|
||||||
|
CHECK(std::isinf(cbor[0].get<float>()));
|
||||||
|
CHECK(cbor[1] == 1);
|
||||||
|
|
||||||
|
// UBJSON: a high-precision number too large for number_float_t
|
||||||
|
const auto ubjson = parse_binary_recovering({'H', 'i', 5, '1', 'e', '9', '9', '9'}, json::input_format_t::ubjson);
|
||||||
|
CHECK(ubjson.errors == 1);
|
||||||
|
CHECK(ubjson.value.is_number_float());
|
||||||
|
CHECK(std::isinf(ubjson.value.get<double>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("binary formats stop where the end of an item is not known")
|
||||||
|
{
|
||||||
|
// a byte that begins no item
|
||||||
|
const auto cbor = parse_binary_recovering({0x82, 0x01, 0x1C, 0x02}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor.errors == 1);
|
||||||
|
CHECK(cbor.value == json({1}));
|
||||||
|
|
||||||
|
// a key that is no item: the unused MessagePack byte, a CBOR break
|
||||||
|
// in a map of known size, and the end of a BON8 container
|
||||||
|
const auto msgpack = parse_binary_recovering({0x82, 0xA1, 'a', 0x01, 0xC1, 0x02}, json::input_format_t::msgpack);
|
||||||
|
CHECK(msgpack.errors == 1);
|
||||||
|
CHECK(msgpack.value == json({{"a", 1}}));
|
||||||
|
const auto cbor_break = parse_binary_recovering({0xA2, 0x61, 'a', 0x01, 0xFF, 0x02}, json::input_format_t::cbor);
|
||||||
|
CHECK(cbor_break.errors == 1);
|
||||||
|
CHECK(cbor_break.value == json({{"a", 1}}));
|
||||||
|
const auto bon8 = parse_binary_recovering({0x88, 0x61, 0x91, 0xFE}, json::input_format_t::bon8);
|
||||||
|
CHECK(bon8.errors == 1);
|
||||||
|
CHECK(bon8.value == json({{"a", 1}}));
|
||||||
|
|
||||||
|
// an indefinite-length string inside an indefinite-length string
|
||||||
|
const auto nested = parse_binary_recovering({0x82, 0x01, 0x7F, 0x7F, 0x61, 'a', 0xFF, 0xFF}, json::input_format_t::cbor);
|
||||||
|
CHECK(nested.errors == 1);
|
||||||
|
CHECK(nested.value == json({1}));
|
||||||
|
|
||||||
|
// a BJData ndarray whose element type has no name: the object that
|
||||||
|
// holds the ndarray was already begun
|
||||||
|
const auto ndarray = parse_binary_recovering({'[', '$', 0x01, '#', '[', '$', 'i', '#', 'i', 2, 2, 3}, json::input_format_t::bjdata);
|
||||||
|
CHECK(ndarray.errors == 1);
|
||||||
|
CHECK(ndarray.balanced);
|
||||||
|
CHECK(ndarray.value == json::object());
|
||||||
|
|
||||||
|
// a skipped member that the input ends in
|
||||||
|
const auto truncated = parse_binary_recovering({0xA2, 0x01, 0x82, 0x01}, json::input_format_t::cbor);
|
||||||
|
CHECK(truncated.errors == 2);
|
||||||
|
CHECK(truncated.balanced);
|
||||||
|
CHECK(truncated.value == json::object());
|
||||||
|
|
||||||
|
// a BSON element of an unknown type in a document whose size cannot be right
|
||||||
|
const auto bson = parse_binary_recovering(bson_document({bson_element(0x10, "a", bson_int32(1)), bson_element(0x42, "x", bytes(3))}, -10), json::input_format_t::bson);
|
||||||
|
CHECK(bson.errors == 1);
|
||||||
|
CHECK(bson.value == json({{"a", 1}, {"x", nullptr}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("changed bytes in binary input")
|
||||||
|
{
|
||||||
|
const json j = {{"a", {1, -2, {{"b", "c"}}, json::array()}}, {"d", {{"e", nullptr}, {"f", true}}}, {"g", 1.5}, {"h", json::binary({1, 2, 3})}, {"i", "\xC3\xA4"}};
|
||||||
|
|
||||||
|
const std::vector<std::pair<json::input_format_t, std::vector<std::uint8_t>>> encodings =
|
||||||
|
{
|
||||||
|
{json::input_format_t::cbor, json::to_cbor(j)},
|
||||||
|
{json::input_format_t::msgpack, json::to_msgpack(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j)},
|
||||||
|
{json::input_format_t::ubjson, json::to_ubjson(j, true, true)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j)},
|
||||||
|
{json::input_format_t::bjdata, json::to_bjdata(j, true, true)},
|
||||||
|
{json::input_format_t::bson, json::to_bson(j)},
|
||||||
|
{json::input_format_t::bon8, json::to_bon8(j)},
|
||||||
|
};
|
||||||
|
const std::vector<std::uint8_t> replacements = {0x00, 0x01, 0x7F, 0x80, 0xC1, 0xD9, 0xE0, 0xF7, 0xFE, 0xFF};
|
||||||
|
|
||||||
|
for (const auto& encoding : encodings)
|
||||||
|
{
|
||||||
|
const auto format = encoding.first;
|
||||||
|
const auto& original = encoding.second;
|
||||||
|
CAPTURE(format)
|
||||||
|
|
||||||
|
std::vector<std::vector<std::uint8_t>> inputs;
|
||||||
|
for (std::size_t position = 0; position < original.size(); ++position)
|
||||||
|
{
|
||||||
|
for (const auto replacement : replacements)
|
||||||
|
{
|
||||||
|
auto changed = original;
|
||||||
|
changed[position] = replacement;
|
||||||
|
inputs.push_back(changed);
|
||||||
|
}
|
||||||
|
auto removed = original;
|
||||||
|
removed.erase(removed.begin() + static_cast<std::ptrdiff_t>(position));
|
||||||
|
inputs.push_back(removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& input : inputs)
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
const auto result = parse_binary_recovering(input, format);
|
||||||
|
CHECK(result.balanced);
|
||||||
|
CHECK(result.errors <= input.size() + 1);
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// an error is reported exactly if reading into a JSON value
|
||||||
|
// fails, and the first one is the same (under JSON_NOEXCEPTION,
|
||||||
|
// that reading aborts instead of throwing)
|
||||||
|
const auto message = binary_error_message(input, format);
|
||||||
|
CHECK(result.ok == message.empty());
|
||||||
|
if (!result.ok && result.errors < 100)
|
||||||
|
{
|
||||||
|
CHECK(result.messages.front() == message);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("JSON text")
|
||||||
|
{
|
||||||
|
// the parser stopped, but reported success
|
||||||
|
json j;
|
||||||
|
RecoveringParser sax(j);
|
||||||
|
CHECK(!json::sax_parse("[1,2,3,]", &sax));
|
||||||
|
CHECK(sax.errors == 1);
|
||||||
|
CHECK(j == json({1, 2, 3}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the SAX parsers of the library stop")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
CHECK(json::from_cbor(std::vector<std::uint8_t> {0x9F}, true, false).is_discarded());
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<std::uint8_t> {0x9F}), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::parse("[1,2,3,]", nullptr, false).is_discarded());
|
||||||
|
CHECK(!json::accept("[1,2,3,]"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("issue #5676 - SAX parsing of CBOR tags")
|
||||||
|
{
|
||||||
|
const json expected = json::binary({1, 2, 3}, 42);
|
||||||
|
const auto cbor = json::to_cbor(expected);
|
||||||
|
|
||||||
|
nlohmann::detail::json_sax_acceptor<json> acceptor;
|
||||||
|
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor));
|
||||||
|
CHECK_FALSE(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::error));
|
||||||
|
|
||||||
|
CHECK(json::sax_parse(cbor, &acceptor, json::input_format_t::cbor,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::ignore));
|
||||||
|
|
||||||
|
json parsed;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> sax(parsed);
|
||||||
|
CHECK(json::sax_parse(cbor, &sax, json::input_format_t::cbor,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
CHECK(parsed == expected);
|
||||||
|
|
||||||
|
json iterator_parsed;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> iterator_sax(iterator_parsed);
|
||||||
|
CHECK(json::sax_parse(cbor.begin(), cbor.end(), &iterator_sax, json::input_format_t::cbor,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
CHECK(iterator_parsed == expected);
|
||||||
|
|
||||||
|
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
json span_parsed;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::string_input_adapter_type> span_sax(span_parsed);
|
||||||
|
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(cbor.data(), cbor.size()), &span_sax,
|
||||||
|
json::input_format_t::cbor, true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
CHECK(span_parsed == expected);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const std::string text = "null";
|
||||||
|
CHECK(json::sax_parse(text, &acceptor, json::input_format_t::json,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
CHECK(json::sax_parse(text.begin(), text.end(), &acceptor, json::input_format_t::json,
|
||||||
|
true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
#ifndef JSON_TEST_DEPRECATED_FUNCTIONS_DELETED
|
||||||
|
CHECK(json::sax_parse(nlohmann::detail::span_input_adapter(text.data(), text.size()), &acceptor,
|
||||||
|
json::input_format_t::json, true, false, false, json::cbor_tag_handler_t::store));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TEST_CASE("MessagePack SAX aborts")
|
||||||
|
{
|
||||||
|
SECTION("start_array(len)")
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> const v = {0x93, 0x01, 0x02, 0x03};
|
||||||
|
SaxCountdown scp(0);
|
||||||
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("start_object(len)")
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||||
|
SaxCountdown scp(0);
|
||||||
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("key()")
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> const v = {0x81, 0xa3, 0x66, 0x6F, 0x6F, 0xc2};
|
||||||
|
SaxCountdown scp(1);
|
||||||
|
CHECK(!json::sax_parse(v, &scp, json::input_format_t::msgpack));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("issue #5405 - a user-defined SAX consumer is unaffected by the internal DOM reserve optimization")
|
||||||
|
{
|
||||||
|
// the reserve() call is local to json_sax_dom_parser / json_sax_dom_callback_parser;
|
||||||
|
// a custom SAX consumer that does not touch a DOM array sees identical events
|
||||||
|
json j = json::array();
|
||||||
|
for (int i = 0; i < 100; ++i)
|
||||||
|
{
|
||||||
|
j.push_back(i);
|
||||||
|
}
|
||||||
|
const auto packed = json::to_msgpack(j);
|
||||||
|
|
||||||
|
SaxCountdown scp(1000000); // large enough to never trigger an abort
|
||||||
|
CHECK(json::sax_parse(packed, &scp, json::input_format_t::msgpack));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("MessagePack nesting does not consume the call stack - SAX interface")
|
||||||
|
{
|
||||||
|
// see the test case of the same name in unit-msgpack.cpp (#5104)
|
||||||
|
std::vector<uint8_t> input(300000, 0x91);
|
||||||
|
input.push_back(0x01); // innermost value
|
||||||
|
|
||||||
|
SaxCountdown accept_all(600001);
|
||||||
|
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::msgpack));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("MessagePack SAX parsing stops at every event")
|
||||||
|
{
|
||||||
|
// Containers are opened and closed by the loop that reads them; a SAX
|
||||||
|
// handler that rejects any event - including the end of a nested
|
||||||
|
// container - must stop the parse right there.
|
||||||
|
const auto count_events = [](const std::vector<std::uint8_t>& input)
|
||||||
|
{
|
||||||
|
int events = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
SaxCountdown scp(events);
|
||||||
|
if (json::sax_parse(input, &scp, json::input_format_t::msgpack))
|
||||||
|
{
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
++events;
|
||||||
|
REQUIRE(events < 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 20 events: every container kind closes inside another one
|
||||||
|
const json j = json::parse(R"({"a": [1, {"b": []}], "c": {"d": [[2]]}})");
|
||||||
|
CHECK(count_events(json::to_msgpack(j)) == 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -1580,3 +1580,94 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -78,7 +78,8 @@ TEST_CASE("detail::bit_cast")
|
|||||||
|
|
||||||
// to and from byte arrays
|
// to and from byte arrays
|
||||||
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
||||||
CHECK(bit_cast<std::uint32_t>(bytes) == 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);
|
||||||
|
|
||||||
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,14 +48,16 @@ namespace
|
|||||||
class chunked_streambuf : public std::streambuf
|
class chunked_streambuf : public std::streambuf
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
// The first @a unbuffered characters are served without a get area, the
|
||||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
// 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)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
int_type underflow() override
|
int_type underflow() override
|
||||||
{
|
{
|
||||||
if (m_chunk_size == 0)
|
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||||
{
|
{
|
||||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
||||||
}
|
}
|
||||||
@@ -77,7 +79,7 @@ class chunked_streambuf : public std::streambuf
|
|||||||
|
|
||||||
int_type uflow() override
|
int_type uflow() override
|
||||||
{
|
{
|
||||||
if (m_chunk_size == 0)
|
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||||
{
|
{
|
||||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
||||||
}
|
}
|
||||||
@@ -88,6 +90,7 @@ class chunked_streambuf : public std::streambuf
|
|||||||
std::string m_data;
|
std::string m_data;
|
||||||
std::size_t m_pos = 0;
|
std::size_t m_pos = 0;
|
||||||
std::size_t m_chunk_size;
|
std::size_t m_chunk_size;
|
||||||
|
std::size_t m_unbuffered;
|
||||||
};
|
};
|
||||||
|
|
||||||
// how a test reads from the stream
|
// how a test reads from the stream
|
||||||
@@ -101,9 +104,9 @@ enum class read_mode
|
|||||||
// Read @a input from a chunked_streambuf and describe everything observable:
|
// Read @a input from a chunked_streambuf and describe everything observable:
|
||||||
// the values or the exception, the stream state after each value, and the
|
// the values or the exception, the stream state after each value, and the
|
||||||
// characters left in the stream.
|
// characters left in the stream.
|
||||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
|
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
|
||||||
{
|
{
|
||||||
chunked_streambuf buf(input, chunk_size);
|
chunked_streambuf buf(input, chunk_size, unbuffered);
|
||||||
std::istream is(&buf);
|
std::istream is(&buf);
|
||||||
std::string result;
|
std::string result;
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
@@ -212,6 +215,31 @@ 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)
|
#endif // !defined(JSON_NOEXCEPTION)
|
||||||
|
|
||||||
SECTION("values are the same as when parsing from a string")
|
SECTION("values are the same as when parsing from a string")
|
||||||
|
|||||||
@@ -139,9 +139,12 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces]
|
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,
|
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True,
|
||||||
lstrip_blocks=True, keep_trailing_newline=True)
|
lstrip_blocks=True, keep_trailing_newline=True)
|
||||||
template = env.get_template('nlohmann_json.natvis.j2')
|
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)
|
natvis = template.render(namespaces=namespaces)
|
||||||
|
|
||||||
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')
|
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')
|
||||||
|
|||||||
Reference in new issue
Block a user