Compare commits

..
Author SHA1 Message Date
Niels Lohmann e19efec1d3 docs: clarify document inputs, decoding, get<T>() and editing
Address review comments: say which inputs are borrowed and that a wide
string pointer is an extra input, which values are decoded during parsing,
that get<T>() reads only built-in types directly, all forms of set() and
that the document's insert() is array-only, and how to store wide strings.

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

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

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

No files matched your search

+3 -3
View File
@@ -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@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
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@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
@@ -37,14 +37,6 @@ jobs:
var matchArtifact = artifacts.data.artifacts.filter((artifact) => { 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,
@@ -59,11 +51,9 @@ 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.
- if: steps.download.outputs.found == 'true' - run: unzip -o pr.zip -d ./pr_artifact
run: unzip -o pr.zip -d ./pr_artifact
- name: 'Comment on PR' - 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 }}
+1 -1
View File
@@ -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@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+2 -2
View File
@@ -54,7 +54,7 @@ jobs:
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1 uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.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@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
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/
+1 -1
View File
@@ -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@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
+1 -1
View File
@@ -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@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with: with:
sarif_file: semgrep.sarif sarif_file: semgrep.sarif
if: always() if: always()
+1 -2
View File
@@ -341,14 +341,13 @@ 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.sha256(url.encode()).hexdigest()[:12] digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin')) 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)
+25 -1
View File
@@ -23,6 +23,9 @@ Checks whether the input is valid JSON.
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to 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.
@@ -35,9 +38,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a `#!c FILE` pointer (throws if null) - a `#!c 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` - 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 instance. : a compatible iterator type, for instance.
@@ -125,6 +131,21 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
--8<-- "examples/accept__iterator_pair.output" --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
@@ -151,6 +172,9 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)). ([`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"
+19
View File
@@ -44,6 +44,22 @@ 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),
@@ -343,6 +359,9 @@ 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
+34 -1
View File
@@ -25,6 +25,10 @@ static basic_json parse(IteratorType first, SentinelType last,
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to 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`
@@ -34,9 +38,12 @@ 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` - 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 instance. : a compatible iterator type, for instance.
@@ -112,6 +119,13 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the 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
@@ -250,6 +264,22 @@ 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
@@ -280,6 +310,9 @@ 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,6 +31,10 @@ Read from input and generate SAX events
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to 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
@@ -42,8 +46,12 @@ 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
@@ -160,6 +168,9 @@ use the same file with stdio functions, but it must not wait for another thread
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)). ([`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"
@@ -115,6 +115,12 @@ points apply to every edit:
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is [`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,17 +21,19 @@ static basic_json_document parse(IteratorType first, IteratorType last,
Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same 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 take the same inputs as `BasicJsonType::parse()`. Byte the input) instead of a tree of `BasicJsonType` values. They take every input `BasicJsonType::parse()` takes, and in
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous addition a pointer to a null-terminated wide string (which `BasicJsonType::parse()` rejects). Contiguous inputs of
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an single-byte characters that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, which
owned copy of a borrowable input as well. must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns -- see the
Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input
as well.
## Template parameters ## Template parameters
`InputType` `InputType`
: A compatible input, for instance: : A compatible input, for instance:
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters - a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), or a C-style array of single-byte characters
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`, - 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.
@@ -47,6 +49,10 @@ owned copy of a borrowable input as well.
`#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs `#!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)
@@ -155,3 +161,5 @@ 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`.
@@ -143,6 +143,12 @@ still refers to it -- including ones taken before the change -- reads the new va
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text - [**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.
@@ -0,0 +1,18 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// a valid JSON text, given as UTF-16
const std::u16string valid_text = u"{\"name\":\"\u00e9\"}";
// an invalid JSON text, given as UTF-16
const std::u16string invalid_text = u"{\"name\":\"\u00e9\",}";
std::cout << std::boolalpha
<< json::accept(valid_text) << ' '
<< json::accept(invalid_text) << '\n';
}
@@ -0,0 +1 @@
true false
@@ -0,0 +1,27 @@
#include <iostream>
#include <string>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// the same JSON text, given in different encodings
const std::string utf8 = "{\"name\":\"\u00e9\"}";
const std::u16string utf16 = u"{\"name\":\"\u00e9\"}";
const std::u32string utf32 = U"{\"name\":\"\u00e9\"}";
const std::wstring wide = L"{\"name\":\"\u00e9\"}";
// the encoding is deduced from the character type of the input, and all
// of them yield the same value
std::cout << json::parse(utf8) << '\n'
<< json::parse(utf16) << '\n'
<< json::parse(utf32) << '\n'
<< json::parse(wide) << '\n';
// a wide string literal works as well: u"..." is UTF-16, U"..." is UTF-32
std::cout << json::parse(u"[1, 2, 3]") << '\n';
// iterators over wide characters work as well
std::cout << json::parse(utf16.begin(), utf16.end()) << '\n';
}
@@ -0,0 +1,6 @@
{"name":"é"}
{"name":"é"}
{"name":"é"}
{"name":"é"}
[1,2,3]
{"name":"é"}
+13
View File
@@ -38,6 +38,18 @@ which avoids repeating it:
--8<-- "examples/get_to.output" --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:
@@ -234,3 +246,4 @@ The conversions above are built in for standard types. To make the same syntax w
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying - [`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
+4 -1
View File
@@ -1,7 +1,9 @@
# 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). instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
into a value; see [Zero-copy JSON views](json_view.md).
## From C++ values ## From C++ values
@@ -101,3 +103,4 @@ 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
+17
View File
@@ -287,6 +287,23 @@ external dependency and is not defined by default.
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md). 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,6 +85,18 @@ 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
@@ -93,3 +105,4 @@ a.erase(1); // [1,3,4] (erase by index)
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch - [`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
+30 -1
View File
@@ -18,6 +18,7 @@ flowchart LR
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be 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
@@ -32,12 +33,18 @@ 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. A single input may contain only one JSON value. The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
is read as UTF-16 or UTF-32 -- depending on the character type -- and converted to UTF-8; see the
[`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value.
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format. 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
@@ -98,6 +105,26 @@ The library offers two parsing models:
You can influence a DOM parse without switching to the SAX interface by passing a 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 strings that contain escape sequences are decoded right away
(into a buffer owned by the document). 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
@@ -108,6 +135,7 @@ options.
## 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
@@ -115,3 +143,4 @@ options.
- [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
- [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
@@ -81,6 +81,9 @@ Invalid UTF-8 is rejected while parsing, not just while serializing:
- In JSON text, an ill-formed UTF-8 byte in a string is a - 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).
-5
View File
@@ -339,11 +339,6 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
- `output_stream_adapter` writes to a `std::ostream`. - `output_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,
+2 -2
View File
@@ -1,7 +1,7 @@
# Debugging # Debugging
This page collects the library's built-in debugger integrations and other debugging-related features. They are This page collects the library's built-in debugger integrations and other debugging-related features. They are not
not linked from a single place elsewhere in the docs, so are collected here. covered by any single page elsewhere in the docs, so they are collected here.
## Visual Studio (natvis) ## Visual Studio (natvis)
+2 -1
View File
@@ -99,7 +99,8 @@ As this global context comes at the price of storing one additional pointer per
Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type. Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information. See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information, and
[Debugging](debugging.md) for the debugger integrations of the library.
## Parse errors ## Parse errors
+4 -3
View File
@@ -81,8 +81,9 @@ rejected by default, and the library never adds them when serializing.
The library supports **Unicode input** as follows: The library supports **Unicode input** as follows:
- Only **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1). - **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers. - `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively; `std::wstring` works as well and is interpreted as UTF-16 or UTF-32, depending on the width of the platform's `wchar_t`. These encodings cannot be read from a `FILE*` or a `std::istream`, which carry bytes only.
- Ill-formed UTF-16 or UTF-32 input is rejected: a lone surrogate, a high surrogate that is not followed by its low surrogate, and a code point above `U+10FFFF` all yield a parse error. The same holds for iterators over 2- or 4-byte characters.
- Other encodings such as Latin-1 or ISO 8859-1 are **not** supported and will yield parse or serialization errors. - 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.
@@ -146,7 +147,7 @@ json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers? Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
As described [above](#parse-errors-reading-non-ascii-characters), the library assumes UTF-8 as encoding. To store a wide string, you need to change the encoding. As described [above](#parse-errors-reading-non-ascii-characters), the library stores its strings as UTF-8. To store a wide string as a string, convert it to a UTF-8 encoded `std::string` first, as in the example below.
!!! example !!! example
+7 -3
View File
@@ -6,7 +6,9 @@ JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-
magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like 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. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
`<nlohmann/json_view.hpp>` adds zero-copy views and editable documents. See
[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
OSS-Fuzz. OSS-Fuzz.
@@ -58,7 +60,8 @@ Conda, and more), and pkg-config.
--- ---
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more. Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
and more.
[:octicons-arrow-right-24: Features](features/index.md) [:octicons-arrow-right-24: Features](features/index.md)
@@ -74,7 +77,8 @@ Conda, and more), and pkg-config.
--- ---
The complete reference for `basic_json` and its member functions, types, and related classes. The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
`json_document` and `json_view`.
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md) [:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
+12 -12
View File
@@ -6,7 +6,7 @@
"": { "": {
"name": "check-mermaid", "name": "check-mermaid",
"dependencies": { "dependencies": {
"jsdom": "30.1.3", "jsdom": "30.1.2",
"mermaid": "11.17.2" "mermaid": "11.17.2"
} }
}, },
@@ -40,9 +40,9 @@
} }
}, },
"node_modules/@asamuzakjp/dom-selector": { "node_modules/@asamuzakjp/dom-selector": {
"version": "10.0.3", "version": "9.2.4",
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz", "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==", "integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"bidi-js": "^1.1.0", "bidi-js": "^1.1.0",
@@ -1202,13 +1202,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/jsdom": { "node_modules/jsdom": {
"version": "30.1.3", "version": "30.1.2",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==", "integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@asamuzakjp/css-color": "^7.1.3", "@asamuzakjp/css-color": "^7.1.3",
"@asamuzakjp/dom-selector": "^10.0.3", "@asamuzakjp/dom-selector": "^9.2.4",
"@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.12.1", "undici": "^8.11.2",
"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.12.2", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==", "integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=22.19.0" "node": ">=22.19.0"
+1 -1
View File
@@ -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.3", "jsdom": "30.1.2",
"mermaid": "11.17.2" "mermaid": "11.17.2"
} }
} }
+33 -192
View File
@@ -8,18 +8,17 @@
#pragma once #pragma once
#include <algorithm> // count, find, max, min #include <algorithm> // count, find, 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> // declval, move, pair #include <utility> // pair, declval
#include <vector> // vector #include <vector> // vector
#ifndef JSON_NO_IO #ifndef JSON_NO_IO
@@ -73,82 +72,6 @@ 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.
*/ */
@@ -286,7 +209,6 @@ 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;
@@ -373,20 +295,37 @@ 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
// the character is in the get area: step over it, as sbumpc() does // sbumpc() refills the get area if it is exhausted
const char* const next = streambuf_access::get_ptr(sb); const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb))) if (JSON_HEDLEY_UNLIKELY(refill))
{ {
current_in_get_area = true; save_token_segment();
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 get_character_slow();
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
#if !JSON_DISABLE_STREAM_BULK_SCAN
current_in_get_area = false;
#endif
is->clear(is->rdstate() | std::ios::eofbit);
}
#if !JSON_DISABLE_STREAM_BULK_SCAN
else if (JSON_HEDLEY_UNLIKELY(refill))
{
// the consumed character is at gptr() - 1, unless the streambuf
// has no get area and returned it from uflow()
const char* const next = streambuf_access::get_ptr(sb);
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
}
else
{
current_in_get_area = true;
}
#endif
return res;
} }
#endif #endif
@@ -395,18 +334,6 @@ 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)))
@@ -494,7 +421,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
{ {
token_carry.copy_to(out); out = token_carry;
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
out.insert(out.end(), token_segment, current_end()); out.insert(out.end(), token_segment, current_end());
@@ -507,47 +434,6 @@ 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
@@ -571,11 +457,6 @@ 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
@@ -596,53 +477,16 @@ class input_stream_adapter
{ {
if (token_segment != nullptr) if (token_segment != nullptr)
{ {
token_carry.append(token_segment, streambuf_access::get_end(sb)); token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
token_segment = nullptr; 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;
@@ -681,16 +525,13 @@ 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
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init) std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
/// start of the current token's part in the get area, or nullptr /// 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
+58 -284
View File
@@ -8,14 +8,14 @@
#pragma once #pragma once
#include <algorithm> // all_of, copy #include <algorithm> // all_of
#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> // memchr, memcmp, memcpy #include <cstring> // 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,38 +80,14 @@ 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)
{ {
@@ -327,12 +303,6 @@ 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
@@ -358,7 +328,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() && !is_dash(s) && !(s[0] >= '0' && s[0] <= '9'))) if (JSON_HEDLEY_UNLIKELY(!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9')))
{ {
return array_index_status::not_a_number; return array_index_status::not_a_number;
} }
@@ -505,40 +475,9 @@ 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 auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/) static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
-> 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);
@@ -549,12 +488,6 @@ 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
{ {
@@ -787,103 +720,48 @@ 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] root the value the pointer starts at @param[in] ptr 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: an array an existing value, or does so in a way that is not plain: a
index that is malformed or out of range, or a "-", or a reference reference token with an escape, an array index that is malformed or
token with an escape. No exception is out of range, or a "-". No exception is thrown for these; callers
thrown for these; callers use @ref json_pointer instead, which use @ref json_pointer instead, which handles them.
handles them.
*/ */
template<typename BasicJsonType> template<typename BasicJsonType>
static BasicJsonType* resolve_plain(BasicJsonType* root, const string_t& path, const std::size_t length, string_t& token, plain_steps<BasicJsonType>& cache) static BasicJsonType* resolve_plain(BasicJsonType* ptr, const string_t& path, const std::size_t length, string_t& token)
{ {
auto& steps = cache.steps; for (std::size_t pos = 0; pos < length;)
// 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
// which must not have an escape std::size_t end = path.find_first_of('/', pos + 1);
const char* const begin = path.data() + pos + 1; if (end == string_t::npos || end > length)
const char* const limit = path.data() + length;
const char* end = begin;
while (end < limit && *end != '/')
{ {
if (JSON_HEDLEY_UNLIKELY(*end == '~')) end = length;
}
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()))
{ {
@@ -895,24 +773,8 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
// an index of up to 18 digits without a leading zero fits into size_type typename BasicJsonType::size_type idx{};
// (9 digits for a 32-bit size_type); everything else is left to json_pointer if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
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;
} }
@@ -931,8 +793,7 @@ class json_pointer
default: default:
return nullptr; return nullptr;
} }
pos = static_cast<std::size_t>(end - path.data()); pos = end;
steps.emplace_back(pos, ptr);
} }
return ptr; return ptr;
@@ -1046,7 +907,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(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(
@@ -1117,7 +978,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(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));
@@ -1179,7 +1040,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return nullptr; return nullptr;
@@ -1256,7 +1117,7 @@ class json_pointer
case detail::value_t::array: case detail::value_t::array:
{ {
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token))) if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
{ {
// "-" always fails the range check // "-" always fails the range check
return false; return false;
@@ -1337,32 +1198,33 @@ class json_pointer
} }
result.reserve(token_count); result.reserve(token_count);
// extract the reference tokens: every one runs from after a slash to // extract the reference tokens:
// the next slash or the end of the string; a token without a '~' (the // - slash: position of the last read slash (or end of string)
// common case) is stored as it is, others are checked and unescaped // - start: position after the previous slash
const char* const data = reference_string.data(); for (
const char* const end = data + size; // search for the first slash after the first character
for (const char* start = data + 1;; ++start) std::size_t slash = reference_string.find_first_of('/', 1),
// 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))
{ {
const char* slash = start; // use the text between the beginning of the reference token
bool escaped = false; // (start) and the last slash (slash).
while (slash != end && *slash != '/') const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
{ auto reference_token = string_t(reference_string.data() + start, count);
escaped = escaped || *slash == '~';
++slash;
}
const auto count = static_cast<std::size_t>(slash - start); // check reference tokens are properly escaped; a token without
if (JSON_HEDLEY_LIKELY(!escaped)) // a '~' (the common case) needs neither check nor unescaping
std::size_t pos = reference_token.find_first_of('~');
if (pos != string_t::npos)
{ {
result.emplace_back(start, count); for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
}
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] == '~');
@@ -1376,14 +1238,10 @@ class json_pointer
} }
detail::unescape(reference_token); detail::unescape(reference_token);
result.push_back(std::move(reference_token));
} }
if (slash == end) // finally, store the reference token
{ result.push_back(std::move(reference_token));
break;
}
start = slash;
} }
return result; return result;
@@ -1418,52 +1276,6 @@ 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)
{ {
@@ -1573,7 +1385,6 @@ class json_pointer
} }
}; };
reserve_flattened(object, value, detail::priority_tag<1> {});
enter(value); enter(value);
while (!stack.empty()) while (!stack.empty())
{ {
@@ -1581,9 +1392,8 @@ 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;
// the path of the previous child is replaced by the path of the // drop the path of the previous child
// next one; for an array element in one resize path.resize(stack.back().path_length);
const std::size_t path_length = stack.back().path_length;
if (container->is_array()) if (container->is_array())
{ {
@@ -1597,20 +1407,8 @@ 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;
std::array<char, 24> digits{}; path.push_back('/');
std::size_t pos = digits.size(); append_index(path, i);
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
@@ -1624,7 +1422,6 @@ 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);
@@ -1720,28 +1517,8 @@ 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 = &current->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, token_size); token.append(data + pos + 1, end - pos - 1);
if (token.find_first_of('~') != string_t::npos) if (token.find_first_of('~') != string_t::npos)
{ {
detail::unescape(token); detail::unescape(token);
@@ -1751,13 +1528,10 @@ class json_pointer
{ {
case detail::value_t::null: case detail::value_t::null:
{ {
// a first child 0 makes the value an array without a look-up if (arrays.contains(data, pos))
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 = &current->operator[](array_index<BasicJsonType>(token)); current = &current->operator[](array_index<BasicJsonType>(token));
} }
else else
File diff suppressed because it is too large. Load diff
@@ -55,66 +55,6 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
/// output_vector_sink also appends writes of at least this size with insert() /// 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
@@ -138,7 +78,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), cursor(vec.data() + vec.size()), limit(cursor) : v(&vec), position(vec.size())
{} {}
output_vector_sink(const output_vector_sink&) = delete; output_vector_sink(const output_vector_sink&) = delete;
@@ -146,7 +86,7 @@ class output_vector_sink
output_vector_sink& operator=(output_vector_sink&&) = delete; output_vector_sink& operator=(output_vector_sink&&) = delete;
output_vector_sink(output_vector_sink&& other) noexcept output_vector_sink(output_vector_sink&& other) noexcept
: v(other.v), cursor(other.cursor), limit(other.limit) : v(other.v), position(other.position)
{ {
other.v = nullptr; other.v = nullptr;
} }
@@ -155,7 +95,7 @@ class output_vector_sink
{ {
if (v != nullptr) if (v != nullptr)
{ {
v->resize(position()); v->resize(position);
} }
} }
@@ -164,11 +104,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(cursor == limit)) if (JSON_HEDLEY_UNLIKELY(position == v->size()))
{ {
grow(1); grow(1);
} }
*cursor++ = c; (*v)[position++] = 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
@@ -177,7 +117,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(space() < length)) if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{ {
if (length >= max_reserve_size) if (length >= max_reserve_size)
{ {
@@ -186,15 +126,10 @@ class output_vector_sink
} }
grow(length); grow(length);
} }
if (length <= 32) if (length != 0)
{ {
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>()); std::memcpy(v->data() + position, s, length * sizeof(CharType));
cursor += length; position += length;
}
else
{
std::memcpy(cursor, s, length * sizeof(CharType));
cursor += length;
} }
} }
@@ -209,37 +144,21 @@ 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(space() < length)) if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
{ {
grow(length); grow(length);
} }
return cursor; return v->data() + position;
} }
/// 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
{ {
cursor += length; position += 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
@@ -247,15 +166,12 @@ 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 min_vector_sink_capacity, so that a small value does not // never less than 64 characters, so that a small value does not
// reallocate for every few bytes // reallocate for every few bytes
const std::size_t written = position(); const std::size_t needed = position + length;
const std::size_t needed = written + length; v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
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
@@ -265,24 +181,20 @@ 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)
{ {
const std::size_t written = position(); v->resize(position);
v->resize(written); if (v->capacity() - position < length)
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, written + length + max_reserve_size)); v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
} }
v->insert(v->end(), s, s + length); v->insert(v->end(), s, s + length);
cursor = v->data() + v->size(); position = v->size();
limit = cursor;
} }
std::vector<CharType, AllocatorType>* v; std::vector<CharType, AllocatorType>* v;
/// the write position: in *v, at the number of characters written so far /// number of characters of *v written so far
CharType* cursor; std::size_t position;
/// the end of *v
CharType* limit;
}; };
/// output adapter for byte vectors /// output adapter for byte vectors
@@ -365,17 +277,9 @@ class output_string_adapter : public output_adapter_protocol<CharType>
/// ///
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can /// 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. /// adapters) via the `output_adapter`-based overloads. Each write still goes
/// /// through one virtual call, exactly as before; only the concrete sinks above
/// A write through the adapter is a virtual call, and for a stream a call with /// avoid it.
/// 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
{ {
@@ -386,121 +290,36 @@ 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)
{ {
if (JSON_HEDLEY_UNLIKELY(used == buffer_size)) oa->write_character(c);
{
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)
{ {
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length)) oa->write_characters(s, 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)
JSON_HEDLEY_ALWAYS_INLINE CharType* reserve(const std::size_t length) noexcept
CharType* reserve(const std::size_t length)
{ {
JSON_ASSERT(length <= max_reserve_size); JSON_ASSERT(length <= max_reserve_size);
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length)) static_cast<void>(length);
{ return reserved.data();
flush();
}
return buffer.data() + used;
} }
/// keep the first @a length characters written to the space from reserve() /// write the first @a length characters written to the space from reserve()
JSON_HEDLEY_ALWAYS_INLINE void commit(const std::size_t length)
void commit(const std::size_t length) noexcept
{ {
used += length; oa->write_characters(reserved.data(), 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;
/// number of characters of @ref buffer not yet passed on std::array<CharType, max_reserve_size> reserved{};
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
+159 -569
View File
@@ -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_container and being threaded through every call to @ref dump, @ref dump_internal 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,14 +80,11 @@ 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
@@ -118,18 +115,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_element(val, current_indent, 0); dump_internal(val, current_indent);
flush(); flush();
} }
JSON_PRIVATE_UNLESS_TESTED: JSON_PRIVATE_UNLESS_TESTED:
/*! /*!
@brief serialize the object or array @a val @brief worker for @ref dump
Writes into the serializer's internal @ref write_buffer instead of issuing Identical in behavior to the historical @ref dump, but writes into the
a virtual call per token; the public @ref dump flushes the buffer once the serializer's internal @ref write_buffer instead of issuing a virtual call
top-level value has been serialized. Scalars are written by @ref dump_scalar, per token. The public @ref dump wraps this and flushes the buffer once the
which @ref dump_element calls without entering this function. top-level value has been serialized.
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
@@ -140,20 +137,20 @@ class serializer
@sa https://github.com/nlohmann/json/issues/5387 @sa https://github.com/nlohmann/json/issues/5387
*/ */
void dump_container(const BasicJsonType& val, void dump_internal(const BasicJsonType& val,
const std::size_t current_indent, const std::size_t current_indent = 0,
const std::size_t depth) const std::size_t depth = 0)
{
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("{}");
@@ -162,27 +159,34 @@ 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_object_key(i->first, "\": "); put_indent(new_indent);
dump_element(i->second, new_indent, depth + 1); put_char('"');
put_literal_indent(",\n", new_indent); dump_escaped(i->first);
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_object_key(i->first, "\": "); put_indent(new_indent);
dump_element(i->second, new_indent, depth + 1); put_char('"');
dump_escaped(i->first);
put_literal("\": ");
dump_internal(i->second, new_indent, depth + 1);
put_literal_indent("\n", current_indent); put_char('\n');
put_indent(current_indent);
put_char('}'); put_char('}');
} }
else else
@@ -193,19 +197,32 @@ 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_object_key(i->first, "\":"); put_char('"');
dump_element(i->second, 0, depth + 1); dump_escaped(i->first);
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_object_key(i->first, "\":"); put_char('"');
dump_element(i->second, 0, depth + 1); dump_escaped(i->first);
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;
} }
@@ -217,24 +234,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
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_element(*i, new_indent, depth + 1); put_indent(new_indent);
put_literal_indent(",\n", new_indent); dump_internal(*i, new_indent, depth + 1);
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());
dump_element(val.m_data.m_value.array->back(), new_indent, depth + 1); put_indent(new_indent);
dump_internal(val.m_data.m_value.array->back(), new_indent, depth + 1);
put_literal_indent("\n", current_indent); put_char('\n');
put_indent(current_indent);
put_char(']'); put_char(']');
} }
else else
@@ -245,44 +265,39 @@ 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_element(*i, 0, depth + 1); dump_internal(*i, current_indent, 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_element(val.m_data.m_value.array->back(), 0, depth + 1); dump_internal(val.m_data.m_value.array->back(), current_indent, 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_container, keeping the containers it has Emits the same bytes as @ref dump_internal, 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
@@ -310,7 +325,8 @@ class serializer
{ {
if (pretty_print) if (pretty_print)
{ {
put_literal_indent("\n", frame.current_indent); put_char('\n');
put_indent(frame.current_indent);
} }
put_char('}'); put_char('}');
@@ -337,13 +353,16 @@ 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_object_key(frame.object_it->first, "\": "); put_literal("\": ");
} }
else else
{ {
put_object_key(frame.object_it->first, "\":"); put_literal("\":");
} }
const BasicJsonType& element = frame.object_it->second; const BasicJsonType& element = frame.object_it->second;
@@ -362,7 +381,8 @@ class serializer
{ {
if (pretty_print) if (pretty_print)
{ {
put_literal_indent("\n", frame.current_indent); put_char('\n');
put_indent(frame.current_indent);
} }
put_char(']'); put_char(']');
@@ -503,7 +523,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_element and @ref dump_value, so that a value is written Shared by @ref dump_internal 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.
@@ -517,13 +537,78 @@ class serializer
{ {
case value_t::string: case value_t::string:
{ {
dump_string_value(*val.m_data.m_value.string); put_char('"');
dump_escaped(*val.m_data.m_value.string);
put_char('"');
return; return;
} }
case value_t::binary: case value_t::binary:
{ {
dump_binary_value(current_indent, *val.m_data.m_value.binary); if (pretty_print)
{
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;
} }
@@ -542,19 +627,19 @@ class serializer
case value_t::number_integer: case value_t::number_integer:
{ {
dump_signed_value(val.m_data.m_value.number_integer); dump_integer(val.m_data.m_value.number_integer);
return; return;
} }
case value_t::number_unsigned: case value_t::number_unsigned:
{ {
dump_unsigned_value(val.m_data.m_value.number_unsigned); dump_integer(val.m_data.m_value.number_unsigned);
return; return;
} }
case value_t::number_float: case value_t::number_float:
{ {
dump_float_value(val.m_data.m_value.number_float); dump_float(val.m_data.m_value.number_float);
return; return;
} }
@@ -577,100 +662,6 @@ 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
@@ -687,220 +678,6 @@ 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
@@ -913,23 +690,14 @@ 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
*/ */
JSON_HEDLEY_NEVER_INLINE void dump_escaped(const string_t& s) 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();
@@ -951,7 +719,6 @@ 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
@@ -1029,7 +796,7 @@ class serializer
put_buffer(string_buffer, bytes); put_buffer(string_buffer, bytes);
bytes = 0; bytes = 0;
} }
put_run(s, i, run); put_string(s, i, i + run);
bytes_after_last_accept = 0; bytes_after_last_accept = 0;
undumped_chars = 0; undumped_chars = 0;
i += run; i += run;
@@ -1069,27 +836,6 @@ 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
@@ -1367,12 +1113,6 @@ 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
@@ -1406,51 +1146,6 @@ 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
@@ -1475,28 +1170,6 @@ 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)
@@ -1739,78 +1412,6 @@ 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
* *
@@ -2168,22 +1769,11 @@ 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
uninitialized_buffer<64> number_buffer; std::array<char, 64> number_buffer{{}};
/// string buffer /// string buffer
uninitialized_buffer<512> string_buffer; std::array<char, 512> string_buffer{{}};
/// the indentation character /// the indentation character
const char indent_char; const char indent_char;
@@ -2203,7 +1793,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;
uninitialized_buffer<write_buffer_size> write_buffer; std::array<char, 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;
}; };
+50 -212
View File
@@ -19,7 +19,6 @@
#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
@@ -6322,7 +6321,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_cbor(j); vector_writer(result, error_handler).write_cbor(j);
return result; return result;
} }
@@ -6332,9 +6331,7 @@ 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> writer(o, error_handler); binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
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
@@ -6342,9 +6339,7 @@ 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> writer(o, error_handler); binary_writer<char>(o, error_handler).write_cbor(j);
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
@@ -6353,7 +6348,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_msgpack(j); vector_writer(result, error_handler).write_msgpack(j);
return result; return result;
} }
@@ -6363,9 +6358,7 @@ 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> writer(o, error_handler); binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
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
@@ -6373,9 +6366,7 @@ 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> writer(o, error_handler); binary_writer<char>(o, error_handler).write_msgpack(j);
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
@@ -6386,7 +6377,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(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;
} }
@@ -6397,9 +6388,7 @@ 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> writer(o, error_handler); binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
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
@@ -6408,9 +6397,7 @@ 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> writer(o, error_handler); binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
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
@@ -6422,7 +6409,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(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;
} }
@@ -6434,9 +6421,7 @@ 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> writer(o, error_handler); binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
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
@@ -6446,9 +6431,7 @@ 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> writer(o, error_handler); binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
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
@@ -6457,7 +6440,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(j));
vector_writer(result, error_handler).write_bson(j); vector_writer(result, error_handler).write_bson(j);
return result; return result;
} }
@@ -6467,9 +6450,7 @@ 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> writer(o, error_handler); binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
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
@@ -6477,9 +6458,7 @@ 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> writer(o, error_handler); binary_writer<char>(o, error_handler).write_bson(j);
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
@@ -6487,7 +6466,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_initial_capacity(j)); result.reserve(detail::binary_reserve_hint(j));
vector_writer(result).write_bon8(j); vector_writer(result).write_bon8(j);
return result; return result;
} }
@@ -6496,18 +6475,14 @@ 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> writer(o); binary_writer<std::uint8_t>(o).write_bon8(j);
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> writer(o); binary_writer<char>(o).write_bon8(j);
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
@@ -6903,72 +6878,6 @@ 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)
@@ -7142,18 +7051,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;
typename json_pointer::template plain_steps<basic_json> steps; const string_t value_key = "value";
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val) const auto try_plain = [&result, &token, &value_key](patch_operations operation, const string_t& path, const basic_json & val)
{ {
if (path.empty()) if (path.empty() || path.find_first_of('~') != string_t::npos)
{ {
return false; return false;
} }
// a missing "value" is reported below // a missing "value" is reported below
const basic_json* const value_ptr = members.value; const auto value_it = val.m_data.m_value.object->find(value_key);
const basic_json& value = value_ptr != nullptr ? *value_ptr : val; const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
if (operation != patch_operations::remove && value_ptr == nullptr) if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
{ {
return false; return false;
} }
@@ -7162,7 +7071,7 @@ public:
{ {
case patch_operations::replace: case patch_operations::replace:
{ {
basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
if (target == nullptr) if (target == nullptr)
{ {
return false; return false;
@@ -7173,7 +7082,7 @@ public:
case patch_operations::test: case patch_operations::test:
{ {
const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token, steps); const basic_json* const target = json_pointer::resolve_plain(&result, path, path.size(), token);
// a failing test is reported below // a failing test is reported below
return target != nullptr && *target == value; return target != nullptr && *target == value;
} }
@@ -7193,17 +7102,13 @@ public:
} }
--slash; --slash;
basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token, steps); basic_json* const parent = json_pointer::resolve_plain(&result, path, slash, token);
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)
{ {
@@ -7259,32 +7164,31 @@ 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](basic_json * found, const auto get_value = [&val](const string_t& op,
const string_t& op, const string_t& member,
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(found == nullptr)) if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))
{ {
detail::throw_patch_missing_member(op, string_t(member), false, val); detail::throw_patch_missing_member(op, 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 && !found->is_string())) if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))
{ {
detail::throw_patch_missing_member(op, string_t(member), true, val); detail::throw_patch_missing_member(op, member, true, val);
} }
// no error: return value // no error: return value
return *found; return it->second;
}; };
// type check: every element of the array must be an object // type check: every element of the array must be an object
@@ -7293,30 +7197,26 @@ 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(members.op, op_member, "op", true).m_data.m_value.string; const string_t& op = *get_value("op", "op", true).m_data.m_value.string;
const string_t& path = *get_value(members.path, op, "path", true).m_data.m_value.string; const string_t& path = *get_value(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, members, val)) if (try_plain(operation, path, 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(members.value, "add", "value", false)); operation_add(ptr, get_value("add", "value", false));
break; break;
} }
@@ -7329,13 +7229,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(members.value, "replace", "value", false); result.at(ptr) = get_value("replace", "value", false);
break; break;
} }
case patch_operations::move: case patch_operations::move:
{ {
const string_t& from_path = *get_value(members.from, "move", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("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)))
@@ -7358,7 +7258,7 @@ public:
case patch_operations::copy: case patch_operations::copy:
{ {
const string_t& from_path = *get_value(members.from, "copy", "from", true).m_data.m_value.string; const string_t& from_path = *get_value("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"
@@ -7376,7 +7276,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(members.value, "test", "value", false)); success = (result.at(ptr) == get_value("test", "value", false));
} }
JSON_INTERNAL_CATCH (out_of_range&) JSON_INTERNAL_CATCH (out_of_range&)
{ {
@@ -7468,18 +7368,13 @@ 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);
auto& members = *entry.m_data.m_value.object; entry["op"] = op;
auto hint = members.end(); entry["path"] = path;
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)
{ {
json_pointer::add_flattened(members, hint, string_t("value"), *value, detail::priority_tag<2> {}); entry["value"] = *value;
} }
entry.set_parents();
result.push_back(std::move(entry)); result.push_back(std::move(entry));
} }
@@ -7501,62 +7396,6 @@ 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)
{ {
@@ -7637,9 +7476,8 @@ 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, TargetValues& target_values, const string_t& path, std::vector<const basic_json*>& 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
@@ -7853,7 +7691,7 @@ public:
case value_t::object: case value_t::object:
{ {
diff_target_values target_values; std::vector<const basic_json*> 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))
{ {
+488 -3176
View File
File diff suppressed because it is too large. Load diff
-275
View File
@@ -492,278 +492,3 @@ TEST_CASE("binary_reserve_hint never over-reserves")
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5); 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
-52
View File
@@ -285,58 +285,6 @@ TEST_CASE("custom number types: integers beyond 64 bits")
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array); 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
-91
View File
@@ -1580,94 +1580,3 @@ TEST_CASE("invalid UTF-8 at every position is handled like the reference")
} }
} }
} }
TEST_CASE("short strings are written like the reference, as values and as keys")
{
// strings of up to 16 characters (32 with vector instructions) that need no
// escaping are written as a whole (together with the quotes and, for a key,
// the colon); everything else, including a string that is merely short,
// takes the general path.
// Every length around the edges of the size classes of the fast path is
// combined with each kind of byte that must send a string to the general
// path, at each position, and compared with the reference for values and,
// via the value, for keys.
const std::vector<std::string> specials =
{
"\"", "\\", "\n", "\x1f", "\x7f", "\xc3\xa9", "\xe2\x82\xac", "\xf0\x9f\x98\x80", "\xff", "\x80", "\xc3"
};
for (std::size_t length = 0; length <= 36; ++length)
{
const std::string plain(length, 'k');
check_dump_against_reference(plain, false);
for (const auto& special : specials)
{
for (std::size_t pos = 0; pos <= length; ++pos)
{
std::string s = plain;
s.insert(pos, special);
check_dump_against_reference(s, false);
for (const bool ensure_ascii :
{
false, true
})
{
// a key and a value are written alike, in compact and
// in pretty-printed form
const json value = s;
const json object = json::object({{s, 1}});
for (const auto error_handler :
{
json::error_handler_t::replace, json::error_handler_t::keep
})
{
const std::string quoted = value.dump(-1, ' ', ensure_ascii, error_handler);
CHECK(object.dump(-1, ' ', ensure_ascii, error_handler) == "{" + quoted + ":1}");
CHECK(object.dump(2, ' ', ensure_ascii, error_handler) == "{\n " + quoted + ": 1\n}");
}
}
}
}
}
}
TEST_CASE("runs of multi-byte characters are escaped like the reference under ensure_ascii")
{
// a run of well-formed multi-byte characters is escaped straight into the
// write buffer; it has to flush that mid-run, stop at an ASCII character or
// an ill-formed sequence and leave those to the general path, and pick up
// behind what the general path has pending
const std::vector<std::string> characters =
{
"\xc2\x80", "\xdf\xbf", "\xe0\xa0\x80", "\xe2\x82\xac", "\xed\x9f\xbf", "\xef\xbf\xbf",
"\xf0\x90\x80\x80", "\xf0\x9f\x98\x80", "\xf4\x8f\xbf\xbf"
};
const std::vector<std::string> interruptions =
{
"", "a", "\n", "\"", "\x7f", "\xff", "\x80", "\xc3", "\xe2\x82", "\xf0\x9f\x98", "\xed\xa0\x80"
};
for (std::size_t shift = 0; shift <= 13; ++shift)
{
for (const auto& interruption : interruptions)
{
for (const std::size_t count :
{
std::size_t{1}, std::size_t{2}, std::size_t{85}, std::size_t{200}
})
{
std::string s(shift, 'x');
for (std::size_t i = 0; i < count; ++i)
{
s += characters[(i + shift) % characters.size()];
}
s += interruption;
s += characters[shift % characters.size()];
s += characters[(shift + 3) % characters.size()];
check_dump_against_reference(s, shift < 2);
}
}
}
}
+1 -2
View File
@@ -78,8 +78,7 @@ TEST_CASE("detail::bit_cast")
// to and from byte arrays // 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});
// qualified: with a std::array argument, ADL would also find std::bit_cast in C++20 CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
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");
} }
+6 -34
View File
@@ -48,16 +48,14 @@ namespace
class chunked_streambuf : public std::streambuf class chunked_streambuf : public std::streambuf
{ {
public: public:
// The first @a unbuffered characters are served without a get area, the chunked_streambuf(std::string s, std::size_t chunk_size)
// rest in get areas of @a chunk_size. : m_data(std::move(s)), m_chunk_size(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 || m_pos < m_unbuffered) if (m_chunk_size == 0)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
} }
@@ -79,7 +77,7 @@ class chunked_streambuf : public std::streambuf
int_type uflow() override int_type uflow() override
{ {
if (m_chunk_size == 0 || m_pos < m_unbuffered) if (m_chunk_size == 0)
{ {
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof(); return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
} }
@@ -90,7 +88,6 @@ class chunked_streambuf : public std::streambuf
std::string m_data; std::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
@@ -104,9 +101,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::size_t unbuffered = 0) std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
{ {
chunked_streambuf buf(input, chunk_size, unbuffered); chunked_streambuf buf(input, chunk_size);
std::istream is(&buf); std::istream is(&buf);
std::string result; std::string result;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
@@ -215,31 +212,6 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
} }
} }
SECTION("results do not depend on a get area that appears late")
{
// the first characters come without a get area, then the streambuf
// starts to use one: the token that is read at that point is complete
for (const auto& input : corpus())
{
const std::string expected = observe(input, 0, read_mode::extract);
for (const std::size_t unbuffered :
{
1u, 2u, 3u, 5u, 8u, 13u, 21u
})
{
for (const std::size_t chunk_size :
{
1u, 2u, 3u, 7u, 64u
})
{
CAPTURE(input)
CAPTURE(unbuffered)
CAPTURE(chunk_size)
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
}
}
}
}
#endif // !defined(JSON_NOEXCEPTION) #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")
-3
View File
@@ -139,12 +139,9 @@ 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')