Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e19efec1d3 | ||
|
|
5464ccab0e | ||
|
|
0a4c1a2d42 |
No files matched your search
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
@@ -37,14 +37,6 @@ jobs:
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "pr"
|
||||
})[0];
|
||||
// a run that failed before its jobs started (e.g. a stale queued
|
||||
// run cancelled long after its push) has no artifact to report on
|
||||
if (!matchArtifact) {
|
||||
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
|
||||
core.setOutput('found', 'false');
|
||||
return;
|
||||
}
|
||||
core.setOutput('found', 'true');
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
@@ -59,11 +51,9 @@ jobs:
|
||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||
# read only the two expected files by fixed path afterwards. This avoids a
|
||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||
- if: steps.download.outputs.found == 'true'
|
||||
run: unzip -o pr.zip -d ./pr_artifact
|
||||
- run: unzip -o pr.zip -d ./pr_artifact
|
||||
|
||||
- name: 'Comment on PR'
|
||||
if: steps.download.outputs.found == 'true'
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Summary
|
||||
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||
path: tests/benchmarks/json_view/results/
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -341,14 +341,13 @@ def download(url, docs) -> str:
|
||||
raise ValueError(f'not an http(s) URL: {url}')
|
||||
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
||||
# (the scheme is checked above)
|
||||
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
||||
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
||||
data = r.read()
|
||||
ctype = r.headers.get_content_type()
|
||||
path = urllib.parse.unquote(u.path).lstrip('/')
|
||||
ext = os.path.splitext(path)[1]
|
||||
if u.query or not ext or path.endswith('/'):
|
||||
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
|
||||
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
|
||||
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
||||
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
||||
out = os.path.join(docs, rel)
|
||||
|
||||
@@ -23,6 +23,9 @@ Checks whether the input is valid JSON.
|
||||
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||
the iterator type with `operator!=`.
|
||||
|
||||
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
|
||||
low surrogate, and a code point above `U+10FFFF` all make this function return `false`.
|
||||
|
||||
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
|
||||
(i.e., a parse error) nor creates diagnostic information.
|
||||
|
||||
@@ -35,9 +38,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
|
||||
- a `#!c FILE` pointer (throws if null)
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
||||
- a `std::string`
|
||||
- a `std::string` or a `std::string_view` (C++17)
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
|
||||
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
|
||||
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
|
||||
|
||||
`IteratorType`
|
||||
: a compatible iterator type, for instance.
|
||||
@@ -125,6 +131,21 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
--8<-- "examples/accept__iterator_pair.output"
|
||||
```
|
||||
|
||||
??? example "Example: (3) reading from UTF-16 strings"
|
||||
|
||||
The encoding is deduced from the character type of the input. String literals like `u"..."`, `U"..."`, and
|
||||
`L"..."` work as well.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/accept__wide_string.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/accept__wide_string.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
@@ -151,6 +172,9 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
|
||||
a code point above `U+10FFFF`) is rejected in version 3.13.0; before that, some such sequences were accepted or
|
||||
ended the input silently.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -44,6 +44,22 @@ are collected in [Template Parameter Requirements](../../features/types/template
|
||||
- [**json**](../json.md) - default specialization
|
||||
- [**ordered_json**](../ordered_json.md) - a specialization that maintains the insertion order of object keys
|
||||
|
||||
## Documents and views
|
||||
|
||||
Besides `basic_json`, the library offers a second way to hold a parsed JSON text: a
|
||||
[`basic_json_document`](../basic_json_document/index.md), which owns or borrows the text and holds a flat index of the
|
||||
values in it, and a [`basic_json_view`](../basic_json_view/index.md), a read-only handle to one value of such a document.
|
||||
Both are defined in the header `<nlohmann/json_view.hpp>`; see
|
||||
[Zero-copy JSON views](../../features/json_view.md) for the concept and the trade-offs.
|
||||
|
||||
- [**json_document**](../json_document.md) / [**ordered_json_document**](../ordered_json_document.md) - read-only
|
||||
documents of the `basic_json` specialization they belong to
|
||||
- [**json_editable_document**](../json_editable_document.md) /
|
||||
[**ordered_json_editable_document**](../ordered_json_editable_document.md) - documents that can be modified in place
|
||||
- [**json_view**](../json_view.md) / [**ordered_json_view**](../ordered_json_view.md) - views of a document
|
||||
- [**json_editable_view**](../json_editable_view.md) /
|
||||
[**ordered_json_editable_view**](../ordered_json_editable_view.md) - views of an editable document
|
||||
|
||||
## Iterator invalidation
|
||||
|
||||
All operations that add values to an **array** ([`push_back`](push_back.md) , [`operator+=`](operator+=.md),
|
||||
@@ -343,6 +359,9 @@ Access to the JSON value
|
||||
## See also
|
||||
|
||||
- [RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc8259)
|
||||
- [basic_json_document](../basic_json_document/index.md) / [basic_json_view](../basic_json_view/index.md) - parse a JSON
|
||||
text into a flat index and read it through a view
|
||||
- [Zero-copy JSON views](../../features/json_view.md) - when to use a document and a view instead of a `basic_json`
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -25,6 +25,10 @@ static basic_json parse(IteratorType first, SentinelType last,
|
||||
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||
the iterator type with `operator!=`.
|
||||
|
||||
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
|
||||
low surrogate, and a code point above `U+10FFFF` all yield a
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
|
||||
|
||||
## Template parameters
|
||||
|
||||
`InputType`
|
||||
@@ -34,9 +38,12 @@ static basic_json parse(IteratorType first, SentinelType last,
|
||||
- a `FILE` pointer (throws if null)
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters (throws if null)
|
||||
- a `std::string`
|
||||
- a `std::string` or a `std::string_view` (C++17)
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
|
||||
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
|
||||
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
|
||||
|
||||
`IteratorType`
|
||||
: a compatible iterator type, for instance.
|
||||
@@ -112,6 +119,13 @@ By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
!!! tip "Parsing into a document instead of a value"
|
||||
|
||||
[`basic_json_document::parse`](../basic_json_document/parse.md) accepts the same inputs and the same options, but
|
||||
keeps the result as a flat index of the parsed values instead of a tree of `basic_json` values. It is faster and uses
|
||||
less memory when only parts of the document are read, and it also accepts a pointer to a null-terminated wide string,
|
||||
which `parse()` does not. See [Zero-copy JSON views](../../features/json_view.md).
|
||||
|
||||
!!! tip "Performance"
|
||||
|
||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
||||
@@ -250,6 +264,22 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
--8<-- "examples/trailing_commas.output"
|
||||
```
|
||||
|
||||
??? example "Example: parse from UTF-16, UTF-32, and wide strings"
|
||||
|
||||
The encoding is deduced from the character type of the input; `std::u16string` and `std::u32string` are interpreted
|
||||
as UTF-16 and UTF-32, and `std::wstring` uses the platform's `wchar_t` width. String literals like `u"..."`,
|
||||
`U"..."`, and `L"..."` work as well, as do iterators over wide characters.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/parse__wide_string.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```json
|
||||
--8<-- "examples/parse__wide_string.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
@@ -280,6 +310,9 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
time).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) restores `std::fgetc`).
|
||||
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
|
||||
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
|
||||
sequences were accepted or ended the input silently.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ Read from input and generate SAX events
|
||||
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||
the iterator type with `operator!=`.
|
||||
|
||||
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its
|
||||
low surrogate, and a code point above `U+10FFFF` all yield a
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
|
||||
|
||||
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
|
||||
|
||||
## Template parameters
|
||||
@@ -42,8 +46,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
- a `FILE` pointer
|
||||
- a C-style array of characters
|
||||
- a pointer to a null-terminated string of single byte characters
|
||||
- a `std::string` or a `std::string_view` (C++17)
|
||||
- a container `obj` for which `begin(obj)` and `end(obj)` produce a valid pair of iterators
|
||||
(as found via ADL or member functions, with semantics compatible to `std::begin` and `std::end`)
|
||||
- wide characters (`wchar_t`, `char16_t`, `char32_t`), which are converted to UTF-8: a wide string object
|
||||
(`std::wstring`, `std::u16string`, `std::u32string`) or an array (e.g. a wide string literal); a pointer to a
|
||||
null-terminated wide string (e.g. `std::u16string::c_str()`) is not supported
|
||||
|
||||
`IteratorType`
|
||||
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
||||
@@ -160,6 +168,9 @@ use the same file with stdio functions, but it must not wait for another thread
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md)).
|
||||
- Parsing from a `FILE*` locks the file once instead of once per character in version 3.13.0
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md)).
|
||||
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
|
||||
a code point above `U+10FFFF`) is rejected with `parse_error.101` in version 3.13.0; before that, some such
|
||||
sequences were accepted or ended the input silently.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -115,6 +115,12 @@ points apply to every edit:
|
||||
[`source_offset()`](../basic_json_view/source_offset.md) of a value introduced by an edit is
|
||||
`#!cpp static_cast<std::size_t>(-1)`, the same value it reports for a decoded string.
|
||||
|
||||
## See also
|
||||
|
||||
- [basic_json_view](../basic_json_view/index.md) - the read-only handle to a value of a document
|
||||
- [json_document](../json_document.md) - this template for the default specialization `nlohmann::json`
|
||||
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -21,17 +21,19 @@ static basic_json_document parse(IteratorType first, IteratorType last,
|
||||
|
||||
Both overloads accept the same JSON text as [`BasicJsonType::parse()`](../basic_json/parse.md), with the same
|
||||
`ignore_comments`/`ignore_trailing_commas` options, but build a [`basic_json_document`](index.md) (a flat index into
|
||||
the input) instead of a tree of `BasicJsonType` values. They take the same inputs as `BasicJsonType::parse()`. Byte
|
||||
inputs that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, non-contiguous
|
||||
iterators) are read into a copy that the document owns -- see the Notes. Use [`parse_copy()`](parse_copy.md) to get an
|
||||
owned copy of a borrowable input as well.
|
||||
the input) instead of a tree of `BasicJsonType` values. They take every input `BasicJsonType::parse()` takes, and in
|
||||
addition a pointer to a null-terminated wide string (which `BasicJsonType::parse()` rejects). Contiguous inputs of
|
||||
single-byte characters that stay alive can be borrowed; all others (streams, `#!cpp FILE*`, wide characters, which
|
||||
must be converted to UTF-8, and non-contiguous iterators) are read into a copy that the document owns -- see the
|
||||
Notes. Use [`parse_copy()`](parse_copy.md) to get an owned copy of a borrowable input
|
||||
as well.
|
||||
|
||||
## Template parameters
|
||||
|
||||
`InputType`
|
||||
: A compatible input, for instance:
|
||||
|
||||
- a `#!cpp std::string`, `#!cpp std::string_view`, or a C-style array of single-byte characters
|
||||
- a `#!cpp std::string`, a `#!cpp std::string_view` (C++17), or a C-style array of single-byte characters
|
||||
- a pointer to a null-terminated string of single-byte characters (`#!cpp char`, `#!cpp signed char`,
|
||||
`#!cpp unsigned char`, `#!cpp std::uint8_t`)
|
||||
- a container for which `#!cpp obj.data()` and `#!cpp obj.size()` give contiguous single-byte access, e.g.
|
||||
@@ -47,6 +49,10 @@ owned copy of a borrowable input as well.
|
||||
`#!cpp std::string::iterator`; the iterators of single-byte characters are borrowed or read like the byte inputs
|
||||
above, those of wide characters are converted to UTF-8
|
||||
|
||||
Ill-formed UTF-16 and UTF-32 sequences are rejected: a lone surrogate, a high surrogate that is not followed by its low
|
||||
surrogate, and a code point above `U+10FFFF` all yield a
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101).
|
||||
|
||||
## Parameters
|
||||
|
||||
`input` (in)
|
||||
@@ -155,3 +161,5 @@ buffer of a given length, pass a pair of pointers: `#!cpp parse(ptr, ptr + len)`
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
- Ill-formed UTF-16 and UTF-32 input (a lone surrogate, a high surrogate that is not followed by its low surrogate, or
|
||||
a code point above `U+10FFFF`) is rejected with `parse_error.101`.
|
||||
@@ -143,6 +143,12 @@ still refers to it -- including ones taken before the change -- reads the new va
|
||||
|
||||
- [**source_offset**](source_offset.md) - byte offset of this value in the document's source text
|
||||
|
||||
## See also
|
||||
|
||||
- [basic_json_document](../basic_json_document/index.md) - the document a view refers into
|
||||
- [materialize](materialize.md) - build the `BasicJsonType` value of a subtree
|
||||
- [Zero-copy JSON views](../../features/json_view.md) - the concept behind documents and views
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -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":"é"}
|
||||
@@ -38,6 +38,18 @@ which avoids repeating it:
|
||||
--8<-- "examples/get_to.output"
|
||||
```
|
||||
|
||||
!!! info "Getting values out of a document or a view"
|
||||
|
||||
A [view](../api/basic_json_view/index.md) offers the same `get<T>()` conversion. For the built-in types (booleans,
|
||||
numbers, strings, `#!cpp std::vector`, `#!cpp std::map`, ...) it reads the value straight from the document's
|
||||
index, without building a `basic_json` value first; user-defined types with a `from_json()` are converted from a
|
||||
`basic_json` value, see the [list](../api/basic_json_view/get.md). Two conversions never copy at all:
|
||||
[`get_string()`](../api/basic_json_view/get_string.md) returns a string as a view into the source text, and
|
||||
[`number_token()`](../api/basic_json_view/number_token.md) returns a number exactly as it was written. Every other
|
||||
type is obtained from a subtree that is built on demand with
|
||||
[`materialize()`](../api/basic_json_view/materialize.md). See
|
||||
[Zero-copy JSON views](json_view.md#getting-values-out-without-copying).
|
||||
|
||||
The library already knows how to convert to and from the scalar types and the STL containers (such as
|
||||
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
|
||||
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
|
||||
@@ -234,3 +246,4 @@ The conversions above are built in for standard types. To make the same syntax w
|
||||
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
|
||||
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
|
||||
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
|
||||
- [Zero-copy JSON views](json_view.md) - get values out of a document or a view
|
||||
@@ -1,7 +1,9 @@
|
||||
# Creating JSON values
|
||||
|
||||
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
|
||||
instead, see [parsing](parsing/index.md).
|
||||
instead, see [parsing](parsing/index.md). A JSON text can also be parsed into a
|
||||
[document](../api/basic_json_document/index.md) and read through a [view](../api/basic_json_view/index.md) instead of
|
||||
into a value; see [Zero-copy JSON views](json_view.md).
|
||||
|
||||
## From C++ values
|
||||
|
||||
@@ -101,3 +103,4 @@ string `#!json "42"`.
|
||||
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
|
||||
- [Converting values](conversions.md) - which C++ types can be used
|
||||
- [Parsing](parsing/index.md) - create a value from JSON text
|
||||
- [Zero-copy JSON views](json_view.md) - create a document and a view from JSON text instead of a value
|
||||
@@ -287,6 +287,23 @@ external dependency and is not defined by default.
|
||||
|
||||
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
|
||||
|
||||
## `JSON_VIEW_NO_SIMD`
|
||||
|
||||
When defined, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
|
||||
(`<nlohmann/json_view.hpp>`) uses only portable C++ to scan strings, instead of vector instructions (SSE2 on x86-64,
|
||||
NEON on AArch64). The same input is accepted or rejected either way; only the speed differs. It is not defined by
|
||||
default.
|
||||
|
||||
See [full documentation of `JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md).
|
||||
|
||||
## `JSON_VIEW_USE_SSSE3`
|
||||
|
||||
When defined on x86-64, the parser of [`basic_json_document`](../api/basic_json_document/index.md)
|
||||
(`<nlohmann/json_view.hpp>`) validates non-ASCII text in strings with SSSE3 without asking the CPU first. It is not
|
||||
defined by default.
|
||||
|
||||
See [full documentation of `JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md).
|
||||
|
||||
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
|
||||
|
||||
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
||||
|
||||
@@ -85,6 +85,18 @@ json a = {1, 2, 3, 4};
|
||||
a.erase(1); // [1,3,4] (erase by index)
|
||||
```
|
||||
|
||||
## Editing a document
|
||||
|
||||
The operations above work on a `basic_json` value. A JSON text that was parsed into a
|
||||
[document](json_view.md#editing-a-document) can also be changed in place, without building a `basic_json` tree for the
|
||||
parts that are not touched: [`set`](../api/basic_json_document/set.md) replaces a value, adds or replaces an object
|
||||
member, or replaces an array element (also through a JSON pointer),
|
||||
[`push_back`](../api/basic_json_document/push_back.md) appends to an array,
|
||||
[`insert`](../api/basic_json_document/insert.md) inserts into an array (unlike `basic_json::insert`, it has no object
|
||||
overload; members are added with `set`), and [`erase`](../api/basic_json_document/erase.md) removes a member or an
|
||||
element. These functions are available on the
|
||||
editable document types, such as [`json_editable_document`](../api/json_editable_document.md).
|
||||
|
||||
## See also
|
||||
|
||||
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
|
||||
@@ -93,3 +105,4 @@ a.erase(1); // [1,3,4] (erase by index)
|
||||
- [`merge_patch`](../api/basic_json/merge_patch.md) - apply an RFC 7396 merge patch
|
||||
- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements
|
||||
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications
|
||||
- [Editing a document](json_view.md#editing-a-document) - change a parsed document in place
|
||||
@@ -18,6 +18,7 @@ flowchart LR
|
||||
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
|
||||
|
||||
- a string (`#!cpp std::string`, C string, or string literal),
|
||||
- a wide string (`#!cpp std::u16string`, `#!cpp std::u32string`, or `#!cpp std::wstring`, or a wide string literal),
|
||||
- a `#!cpp std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
|
||||
- a `#!cpp FILE*` pointer,
|
||||
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
|
||||
@@ -32,12 +33,18 @@ std::ifstream f("example.json");
|
||||
json data = json::parse(f);
|
||||
```
|
||||
|
||||
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
|
||||
The input must be encoded in UTF-8; other encodings are not supported. The exception is wide-character input, which
|
||||
is read as UTF-16 or UTF-32 -- depending on the character type -- and converted to UTF-8; see the
|
||||
[`parse`](../../api/basic_json/parse.md) page for an example. A single input may contain only one JSON value.
|
||||
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
|
||||
|
||||
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
|
||||
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
|
||||
|
||||
[`basic_json_document::parse`](../../api/basic_json_document/parse.md) accepts the same inputs with the same options,
|
||||
but keeps the result in a document rather than in a `basic_json` value; see
|
||||
[Documents and views](#documents-and-views) below.
|
||||
|
||||
## Strictness and trailing data
|
||||
|
||||
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
||||
@@ -98,6 +105,26 @@ The library offers two parsing models:
|
||||
You can influence a DOM parse without switching to the SAX interface by passing a
|
||||
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
|
||||
|
||||
## Documents and views
|
||||
|
||||
A third model sits between DOM and SAX: [`basic_json_document::parse`](../../api/basic_json_document/parse.md) parses
|
||||
the input into a document that keeps the text and a flat index of the values in it, and
|
||||
[`basic_json_view`](../../api/basic_json_view/index.md) gives read-only access to the values through that index.
|
||||
The whole input is validated during parsing, but only 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
|
||||
|
||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||
@@ -108,6 +135,7 @@ options.
|
||||
## See also
|
||||
|
||||
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
|
||||
- [`basic_json_document::parse`](../../api/basic_json_document/parse.md) - deserialize into a document instead of a value
|
||||
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
|
||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
|
||||
@@ -115,3 +143,4 @@ options.
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||
- [Zero-copy JSON views](../json_view.md) - parse into a document and read it through a view
|
||||
@@ -81,6 +81,9 @@ Invalid UTF-8 is rejected while parsing, not just while serializing:
|
||||
|
||||
- In JSON text, an ill-formed UTF-8 byte in a string is a
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) ("invalid string: ill-formed UTF-8 byte").
|
||||
- In UTF-16 or UTF-32 input, an ill-formed sequence is a
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) as well: a lone surrogate, a high surrogate
|
||||
that is not followed by its low surrogate, and a code point above `U+10FFFF` are all rejected.
|
||||
- In a binary format, a string that is not valid UTF-8 is a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113).
|
||||
|
||||
|
||||
@@ -339,11 +339,6 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
||||
- `output_stream_adapter` writes to a `std::ostream`.
|
||||
- `output_string_adapter` appends to a string.
|
||||
|
||||
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||
|
||||
## Value conversion
|
||||
|
||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Debugging
|
||||
|
||||
This page collects the library's built-in debugger integrations and other debugging-related features. They are
|
||||
not linked from a single place elsewhere in the docs, so are collected here.
|
||||
This page collects the library's built-in debugger integrations and other debugging-related features. They are not
|
||||
covered by any single page elsewhere in the docs, so they are collected here.
|
||||
|
||||
## Visual Studio (natvis)
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@ As this global context comes at the price of storing one additional pointer per
|
||||
|
||||
Now the exception message contains a JSON Pointer `/address/housenumber` that indicates which value has the wrong type.
|
||||
|
||||
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information.
|
||||
See [documentation of `JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) for more information, and
|
||||
[Debugging](debugging.md) for the debugger integrations of the library.
|
||||
|
||||
## Parse errors
|
||||
|
||||
|
||||
@@ -81,8 +81,9 @@ rejected by default, and the library never adds them when serializing.
|
||||
|
||||
The library supports **Unicode input** as follows:
|
||||
|
||||
- Only **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
|
||||
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively. These encodings are not supported when reading from files or other input containers.
|
||||
- **UTF-8** encoded input is supported, which is the default encoding for JSON, according to [RFC 8259](https://tools.ietf.org/html/rfc8259.html#section-8.1).
|
||||
- `std::u16string` and `std::u32string` can be parsed, assuming UTF-16 and UTF-32 encoding, respectively; `std::wstring` works as well and is interpreted as UTF-16 or UTF-32, depending on the width of the platform's `wchar_t`. These encodings cannot be read from a `FILE*` or a `std::istream`, which carry bytes only.
|
||||
- Ill-formed UTF-16 or UTF-32 input is rejected: a lone surrogate, a high surrogate that is not followed by its low surrogate, and a code point above `U+10FFFF` all yield a parse error. The same holds for iterators over 2- or 4-byte characters.
|
||||
- Other encodings such as Latin-1 or ISO 8859-1 are **not** supported and will yield parse or serialization errors.
|
||||
- The library will not replace [Unicode noncharacters](http://www.unicode.org/faq/private_use.html#nonchar1).
|
||||
- Invalid surrogates (e.g., incomplete pairs such as `\uDEAD`) will yield parse errors.
|
||||
@@ -146,7 +147,7 @@ json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control
|
||||
|
||||
Why are wide strings (e.g., `std::wstring`) dumped as arrays of numbers?
|
||||
|
||||
As described [above](#parse-errors-reading-non-ascii-characters), the library assumes UTF-8 as encoding. To store a wide string, you need to change the encoding.
|
||||
As described [above](#parse-errors-reading-non-ascii-characters), the library stores its strings as UTF-8. To store a wide string as a string, convert it to a UTF-8 encoded `std::string` first, as in the example below.
|
||||
|
||||
!!! example
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@ JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-
|
||||
magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like
|
||||
Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no
|
||||
complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times
|
||||
down. See [header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
|
||||
down, `<nlohmann/json_literals.hpp>` holds the user-defined string literals on their own, and
|
||||
`<nlohmann/json_view.hpp>` adds zero-copy views and editable documents. See
|
||||
[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code
|
||||
coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google
|
||||
OSS-Fuzz.
|
||||
|
||||
@@ -58,7 +60,8 @@ Conda, and more), and pkg-config.
|
||||
|
||||
---
|
||||
|
||||
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, and more.
|
||||
Creating, parsing, accessing, and serializing JSON values, JSON Pointer/Patch, binary formats, zero-copy views,
|
||||
and more.
|
||||
|
||||
[:octicons-arrow-right-24: Features](features/index.md)
|
||||
|
||||
@@ -74,7 +77,8 @@ Conda, and more), and pkg-config.
|
||||
|
||||
---
|
||||
|
||||
The complete reference for `basic_json` and its member functions, types, and related classes.
|
||||
The complete reference for `basic_json` and its member functions, types, and related classes, as well as for
|
||||
`json_document` and `json_view`.
|
||||
|
||||
[:octicons-arrow-right-24: API documentation](api/basic_json/index.md)
|
||||
|
||||
|
||||
+12
-12
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "check-mermaid",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.3",
|
||||
"jsdom": "30.1.2",
|
||||
"mermaid": "11.17.2"
|
||||
}
|
||||
},
|
||||
@@ -40,9 +40,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@asamuzakjp/dom-selector": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||
"version": "9.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.1.0",
|
||||
@@ -1202,13 +1202,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "30.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||
"version": "30.1.2",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@asamuzakjp/css-color": "^7.1.3",
|
||||
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
||||
"@bramus/specificity": "^2.4.2",
|
||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||
"@exodus/bytes": "^1.16.0",
|
||||
@@ -1221,7 +1221,7 @@
|
||||
"parse5": "^8.0.1",
|
||||
"saxes": "^6.0.0",
|
||||
"tough-cookie": "^6.0.2",
|
||||
"undici": "^8.12.1",
|
||||
"undici": "^8.11.2",
|
||||
"w3c-xmlserializer": "^6.0.0",
|
||||
"webidl-conversions": "^8.0.1",
|
||||
"whatwg-mimetype": "^5.0.0",
|
||||
@@ -1521,9 +1521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "8.12.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"jsdom": "30.1.3",
|
||||
"jsdom": "30.1.2",
|
||||
"mermaid": "11.17.2"
|
||||
}
|
||||
}
|
||||
@@ -8,18 +8,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // count, find, max, min
|
||||
#include <algorithm> // count, find, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // strlen
|
||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||
#include <limits> // numeric_limits
|
||||
#include <memory> // unique_ptr
|
||||
#include <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
#include <utility> // declval, move, pair
|
||||
#include <utility> // pair, declval
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -73,82 +72,6 @@ enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/*!
|
||||
A growable array of characters for the part of a token that an input adapter
|
||||
copied out of its window: unlike std::vector<char>::push_back(), appending a
|
||||
character takes no reference to a local, and is a few instructions when there
|
||||
is room.
|
||||
*/
|
||||
class token_carry_buffer
|
||||
{
|
||||
public:
|
||||
token_carry_buffer() = default;
|
||||
token_carry_buffer(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
|
||||
token_carry_buffer(token_carry_buffer&& rhs) noexcept
|
||||
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
|
||||
{
|
||||
rhs.m_end = rhs.m_capacity_end = nullptr;
|
||||
}
|
||||
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
|
||||
~token_carry_buffer() = default;
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
m_end = m_data.get();
|
||||
}
|
||||
|
||||
void push_back(char c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
*m_end++ = c;
|
||||
}
|
||||
|
||||
void append(const char* first, const char* last)
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(last - first);
|
||||
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
|
||||
if (n > room)
|
||||
{
|
||||
grow(n);
|
||||
}
|
||||
if (n != 0)
|
||||
{
|
||||
std::memcpy(m_end, first, n);
|
||||
}
|
||||
m_end += n;
|
||||
}
|
||||
|
||||
/// replace the contents of @a out
|
||||
void copy_to(std::vector<char>& out) const
|
||||
{
|
||||
out.assign(m_data.get(), m_end);
|
||||
}
|
||||
|
||||
private:
|
||||
/// make room for @a extra more characters
|
||||
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
|
||||
{
|
||||
const auto size = static_cast<std::size_t>(m_end - m_data.get());
|
||||
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
|
||||
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
|
||||
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (size != 0)
|
||||
{
|
||||
std::memcpy(data.get(), m_data.get(), size);
|
||||
}
|
||||
m_data = std::move(data);
|
||||
m_end = m_data.get() + size;
|
||||
m_capacity_end = m_data.get() + new_capacity;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
|
||||
char* m_end = nullptr;
|
||||
char* m_capacity_end = nullptr;
|
||||
};
|
||||
/*!
|
||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||
buffer. This adapter is a very low level adapter.
|
||||
*/
|
||||
@@ -286,7 +209,6 @@ class input_stream_adapter
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_get_area(rhs.current_in_get_area)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
, no_get_area(rhs.no_get_area)
|
||||
#endif
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
@@ -373,20 +295,37 @@ class input_stream_adapter
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the character is in the get area: step over it, as sbumpc() does
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
current_in_get_area = true;
|
||||
streambuf_access::bump(sb, 1);
|
||||
return std::char_traits<char>::to_int_type(*next);
|
||||
}
|
||||
if (no_get_area)
|
||||
{
|
||||
return get_character_no_get_area();
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
return get_character_slow();
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -395,18 +334,6 @@ class input_stream_adapter
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
commit_lookahead();
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the elements are in the get area: copy them, as sgetn() does, but
|
||||
// without the virtual call
|
||||
const char* const first = streambuf_access::get_ptr(sb);
|
||||
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
|
||||
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
|
||||
{
|
||||
std::memcpy(dest, first, count * sizeof(T));
|
||||
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
|
||||
return count * sizeof(T);
|
||||
}
|
||||
#endif
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
@@ -494,7 +421,7 @@ class input_stream_adapter
|
||||
// last one if @a drop_last.
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
token_carry.copy_to(out);
|
||||
out = token_carry;
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, current_end());
|
||||
@@ -507,47 +434,6 @@ class input_stream_adapter
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// the get area is exhausted, or there is none
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
#endif
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||
// members through a derived class makes forming the member pointers
|
||||
@@ -571,11 +457,6 @@ class input_stream_adapter
|
||||
{
|
||||
(s->*(&streambuf_access::gbump))(n);
|
||||
}
|
||||
// sbumpc() for an exhausted get area
|
||||
static std::streambuf::int_type next_character(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::uflow))();
|
||||
}
|
||||
};
|
||||
|
||||
// one past the character last returned by get_character(), if that is in
|
||||
@@ -596,53 +477,16 @@ class input_stream_adapter
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.append(token_segment, streambuf_access::get_end(sb));
|
||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
||||
token_segment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
#if !JSON_PRECISE_STREAM_POSITION
|
||||
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
|
||||
// every character comes from uflow(), and the token is kept by value.
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
|
||||
{
|
||||
// the get area is exhausted, so this is what sbumpc() does
|
||||
const auto res = streambuf_access::next_character(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
|
||||
{
|
||||
return get_character_no_get_area_other(res);
|
||||
}
|
||||
// current_in_get_area is false: only the first character of a get
|
||||
// area, or the end of the input, is read here
|
||||
if (tracking_token)
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(res));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// @a res came from uflow() of a streambuf without a get area: it is the
|
||||
// end of the input, or the streambuf has a get area now
|
||||
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
|
||||
{
|
||||
if (res == std::char_traits<char>::eof())
|
||||
{
|
||||
current_in_get_area = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
return res;
|
||||
}
|
||||
// the consumed character is at gptr() - 1
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
// character @a c was just read into a new get area (@a in_get_area) or
|
||||
// returned without one
|
||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||
{
|
||||
current_in_get_area = in_get_area;
|
||||
no_get_area = !in_get_area;
|
||||
if (!tracking_token)
|
||||
{
|
||||
return;
|
||||
@@ -681,16 +525,13 @@ class input_stream_adapter
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
/// the part of the current token that was in earlier get areas
|
||||
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||
/// start of the current token's part in the get area, or nullptr
|
||||
const char* token_segment = nullptr;
|
||||
/// whether the character last returned by get_character() is in the get area
|
||||
bool current_in_get_area = false;
|
||||
/// whether a token is tracked at all; the binary readers never start one
|
||||
bool tracking_token = false;
|
||||
/// whether the streambuf had no get area when it was last asked for a
|
||||
/// character
|
||||
bool no_get_area = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // all_of, copy
|
||||
#include <algorithm> // all_of
|
||||
#include <array> // array
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
@@ -80,38 +80,14 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
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
|
||||
std::size_t length = 0;
|
||||
bool plain = true;
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
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;
|
||||
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);
|
||||
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
|
||||
};
|
||||
|
||||
/// 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[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-
|
||||
// character tokens, so "/x" and "/xy" fail alike; "-" and the empty
|
||||
// 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;
|
||||
}
|
||||
@@ -505,40 +475,9 @@ class json_pointer
|
||||
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
|
||||
///
|
||||
/// 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>
|
||||
static auto object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(object.m_data.m_value.object->lower_bound(key), key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
auto it = members.lower_bound(key);
|
||||
if (it != members.end() && !members.key_comp()(key, it->first))
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const auto old_capacity = object.container_capacity();
|
||||
it = members.emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
const auto it = members.find(key);
|
||||
@@ -549,12 +488,6 @@ class json_pointer
|
||||
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_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
|
||||
|
||||
@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] length the number of leading bytes of @a path to resolve; 0 or
|
||||
the position of a slash
|
||||
@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
|
||||
an existing value, or does so in a way that is not plain: an array
|
||||
index that is malformed or out of range, or a "-", or a reference
|
||||
token with an escape. No exception is
|
||||
thrown for these; callers use @ref json_pointer instead, which
|
||||
handles them.
|
||||
an existing value, or does so in a way that is not plain: a
|
||||
reference token with an escape, an array index that is malformed or
|
||||
out of range, or a "-". No exception is thrown for these; callers
|
||||
use @ref json_pointer instead, which handles them.
|
||||
*/
|
||||
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;
|
||||
|
||||
// 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;)
|
||||
for (std::size_t pos = 0; pos < length;)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(path[pos] != '/'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// the reference token between the slash at pos and the next one,
|
||||
// which must not have an escape
|
||||
const char* const begin = path.data() + pos + 1;
|
||||
const char* const limit = path.data() + length;
|
||||
const char* end = begin;
|
||||
while (end < limit && *end != '/')
|
||||
// the reference token between the slash at pos and the next one
|
||||
std::size_t end = path.find_first_of('/', pos + 1);
|
||||
if (end == string_t::npos || end > length)
|
||||
{
|
||||
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;
|
||||
}
|
||||
++end;
|
||||
}
|
||||
|
||||
switch (ptr->type())
|
||||
{
|
||||
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);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
@@ -895,24 +773,8 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
// an index of up to 18 digits without a leading zero fits into size_type
|
||||
// (9 digits for a 32-bit size_type); everything else is left to json_pointer
|
||||
constexpr std::size_t max_digits = sizeof(typename BasicJsonType::size_type) >= 8 ? 18 : 9;
|
||||
const auto digits = static_cast<std::size_t>(end - begin);
|
||||
if (JSON_HEDLEY_UNLIKELY(digits == 0 || digits > max_digits || (digits > 1 && *begin == '0')))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
typename BasicJsonType::size_type idx = 0;
|
||||
for (const char* c = begin; c != end; ++c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(*c < '0' || *c > '9'))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
idx = static_cast<typename BasicJsonType::size_type>((idx * 10) + static_cast<typename BasicJsonType::size_type>(*c - '0'));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(idx >= ptr->m_data.m_value.array->size()))
|
||||
typename BasicJsonType::size_type idx{};
|
||||
if (JSON_HEDLEY_UNLIKELY(parse_array_index<BasicJsonType>(token, idx) != array_index_status::ok || idx >= ptr->m_data.m_value.array->size()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
@@ -931,8 +793,7 @@ class json_pointer
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
pos = static_cast<std::size_t>(end - path.data());
|
||||
steps.emplace_back(pos, ptr);
|
||||
pos = end;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
@@ -1046,7 +907,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
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:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" 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));
|
||||
@@ -1179,7 +1040,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return nullptr;
|
||||
@@ -1256,7 +1117,7 @@ class json_pointer
|
||||
|
||||
case detail::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(is_dash(reference_token)))
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_token == "-"))
|
||||
{
|
||||
// "-" always fails the range check
|
||||
return false;
|
||||
@@ -1337,32 +1198,33 @@ class json_pointer
|
||||
}
|
||||
result.reserve(token_count);
|
||||
|
||||
// extract the reference tokens: every one runs from after a slash to
|
||||
// the next slash or the end of the string; a token without a '~' (the
|
||||
// common case) is stored as it is, others are checked and unescaped
|
||||
const char* const data = reference_string.data();
|
||||
const char* const end = data + size;
|
||||
for (const char* start = data + 1;; ++start)
|
||||
// extract the reference tokens:
|
||||
// - slash: position of the last read slash (or end of string)
|
||||
// - start: position after the previous slash
|
||||
for (
|
||||
// search for the first slash after the first character
|
||||
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;
|
||||
bool escaped = false;
|
||||
while (slash != end && *slash != '/')
|
||||
{
|
||||
escaped = escaped || *slash == '~';
|
||||
++slash;
|
||||
}
|
||||
// use the text between the beginning of the reference token
|
||||
// (start) and the last slash (slash).
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
const auto count = static_cast<std::size_t>(slash - start);
|
||||
if (JSON_HEDLEY_LIKELY(!escaped))
|
||||
// check reference tokens are properly escaped; a token without
|
||||
// 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);
|
||||
}
|
||||
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))
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
{
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
@@ -1376,14 +1238,10 @@ class json_pointer
|
||||
}
|
||||
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
if (slash == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
start = slash;
|
||||
// finally, store the reference token
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1418,52 +1276,6 @@ class json_pointer
|
||||
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
|
||||
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);
|
||||
while (!stack.empty())
|
||||
{
|
||||
@@ -1581,9 +1392,8 @@ class json_pointer
|
||||
// frame, which would invalidate a reference to it
|
||||
const BasicJsonType* const container = stack.back().container;
|
||||
|
||||
// the path of the previous child is replaced by the path of the
|
||||
// next one; for an array element in one resize
|
||||
const std::size_t path_length = stack.back().path_length;
|
||||
// drop the path of the previous child
|
||||
path.resize(stack.back().path_length);
|
||||
|
||||
if (container->is_array())
|
||||
{
|
||||
@@ -1597,20 +1407,8 @@ class json_pointer
|
||||
|
||||
// iterate array and use index as a reference string
|
||||
++stack.back().index;
|
||||
std::array<char, 24> digits{};
|
||||
std::size_t pos = digits.size();
|
||||
std::size_t rest = i;
|
||||
do
|
||||
{
|
||||
digits[--pos] = static_cast<char>('0' + (rest % 10));
|
||||
rest /= 10;
|
||||
}
|
||||
while (rest != 0);
|
||||
const std::size_t count = digits.size() - pos;
|
||||
path.resize(path_length + 1 + count);
|
||||
char* const out = &path[path_length];
|
||||
out[0] = '/';
|
||||
std::memcpy(out + 1, digits.data() + pos, count);
|
||||
path.push_back('/');
|
||||
append_index(path, i);
|
||||
enter(array[i]);
|
||||
}
|
||||
else
|
||||
@@ -1624,7 +1422,6 @@ class json_pointer
|
||||
|
||||
// iterate object and use keys as reference string
|
||||
++stack.back().member;
|
||||
path.resize(path_length);
|
||||
path.push_back('/');
|
||||
detail::escape_into(path, it->first);
|
||||
enter(it->second);
|
||||
@@ -1720,28 +1517,8 @@ class json_pointer
|
||||
{
|
||||
end = key.size();
|
||||
}
|
||||
const std::size_t token_size = end - pos - 1;
|
||||
|
||||
// a token that is a plain index of up to 9 digits, which is
|
||||
// the case for almost all array elements, is not copied
|
||||
std::size_t index = 0;
|
||||
bool plain_index = current->type() == detail::value_t::array && token_size != 0 && token_size <= 9 && (token_size == 1 || data[pos + 1] != '0');
|
||||
for (std::size_t k = pos + 1; plain_index && k < end; ++k)
|
||||
{
|
||||
plain_index = data[k] >= '0' && data[k] <= '9';
|
||||
index = (index * 10) + static_cast<std::size_t>(data[k] - '0');
|
||||
}
|
||||
if (plain_index)
|
||||
{
|
||||
current = ¤t->operator[](static_cast<typename BasicJsonType::size_type>(index));
|
||||
values.push_back(current);
|
||||
ends.push_back(end);
|
||||
pos = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
token.clear();
|
||||
token.append(data + pos + 1, token_size);
|
||||
token.append(data + pos + 1, end - pos - 1);
|
||||
if (token.find_first_of('~') != string_t::npos)
|
||||
{
|
||||
detail::unescape(token);
|
||||
@@ -1751,13 +1528,10 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
// a first child 0 makes the value an array without a look-up
|
||||
if ((token_size == 1 && data[pos + 1] == '0') || arrays.contains(data, pos))
|
||||
if (arrays.contains(data, pos))
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
*current = BasicJsonType(detail::value_t::array);
|
||||
reserve_elements(*current->m_data.m_value.array, 2, detail::priority_tag<1> {});
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
}
|
||||
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()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||
///
|
||||
/// Short strings are the typical payload of a binary value, and a call of
|
||||
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||
/// so on down to a single character; each is a fixed-size load and store.
|
||||
/// Nothing outside of [src, src + length) is read or written.
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= 32);
|
||||
if (length >= 16)
|
||||
{
|
||||
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 16);
|
||||
std::memcpy(last.data(), src + (length - 16), 16);
|
||||
std::memcpy(dst, first.data(), 16);
|
||||
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||
}
|
||||
else if (length >= 8)
|
||||
{
|
||||
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 8);
|
||||
std::memcpy(last.data(), src + (length - 8), 8);
|
||||
std::memcpy(dst, first.data(), 8);
|
||||
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||
}
|
||||
else if (length >= 4)
|
||||
{
|
||||
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||
std::memcpy(first.data(), src, 4);
|
||||
std::memcpy(last.data(), src + (length - 4), 4);
|
||||
std::memcpy(dst, first.data(), 4);
|
||||
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||
}
|
||||
else if (length != 0)
|
||||
{
|
||||
// 1 to 3: the first, the middle, and the last character
|
||||
const CharType a = src[0];
|
||||
const CharType b = src[length >> 1];
|
||||
const CharType c = src[length - 1];
|
||||
dst[0] = a;
|
||||
dst[length >> 1] = b;
|
||||
dst[length - 1] = c;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy @a length characters, of a character type wider than a byte
|
||||
template<typename CharType>
|
||||
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||
{
|
||||
std::memcpy(dst, src, length * sizeof(CharType));
|
||||
}
|
||||
|
||||
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
@@ -138,7 +78,7 @@ class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||
: v(&vec), position(vec.size())
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
@@ -146,7 +86,7 @@ class output_vector_sink
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||
: v(other.v), position(other.position)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
@@ -155,7 +95,7 @@ class output_vector_sink
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position());
|
||||
v->resize(position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,11 +104,11 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
*cursor++ = c;
|
||||
(*v)[position++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
@@ -177,7 +117,7 @@ class output_vector_sink
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
@@ -186,15 +126,10 @@ class output_vector_sink
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length <= 32)
|
||||
if (length != 0)
|
||||
{
|
||||
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
cursor += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||
cursor += length;
|
||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||
position += length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,37 +144,21 @@ class output_vector_sink
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return cursor;
|
||||
return v->data() + position;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
cursor += length;
|
||||
position += length;
|
||||
}
|
||||
|
||||
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(cursor - v->data());
|
||||
}
|
||||
|
||||
/// number of characters that fit the vector behind the write position
|
||||
std::size_t space() const noexcept
|
||||
{
|
||||
return static_cast<std::size_t>(limit - cursor);
|
||||
}
|
||||
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
/// GCC to inline them into the writer
|
||||
@@ -247,15 +166,12 @@ class output_vector_sink
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than min_vector_sink_capacity, so that a small value does not
|
||||
// never less than 64 characters, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
const std::size_t written = position();
|
||||
const std::size_t needed = written + length;
|
||||
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||
const std::size_t needed = position + length;
|
||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
||||
? v->capacity()
|
||||
: (std::max)(needed, v->capacity() * 2)));
|
||||
cursor = v->data() + written;
|
||||
limit = v->data() + v->size();
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
@@ -265,24 +181,20 @@ class output_vector_sink
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t length)
|
||||
{
|
||||
const std::size_t written = position();
|
||||
v->resize(written);
|
||||
if (v->capacity() - written < length)
|
||||
v->resize(position);
|
||||
if (v->capacity() - position < length)
|
||||
{
|
||||
// grow geometrically, with room for the writes that follow, so
|
||||
// that the next grow() need not reallocate right away
|
||||
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
cursor = v->data() + v->size();
|
||||
limit = cursor;
|
||||
position = v->size();
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// the write position: in *v, at the number of characters written so far
|
||||
CharType* cursor;
|
||||
/// the end of *v
|
||||
CharType* limit;
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
@@ -365,17 +277,9 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads.
|
||||
///
|
||||
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||
/// called. Everything written reaches the adapter in the same order and with
|
||||
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||
/// @ref flush can report an error of the adapter (the destructor must not
|
||||
/// throw), so it has to be called once the value is written.
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
@@ -386,121 +290,36 @@ class output_adapter_sink
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||
|
||||
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||
: oa(std::move(other.oa)), used(other.used)
|
||||
{
|
||||
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||
other.used = 0;
|
||||
}
|
||||
|
||||
~output_adapter_sink()
|
||||
{
|
||||
// a normal return has flushed already, so this is for a write that threw
|
||||
if (used != 0)
|
||||
{
|
||||
JSON_TRY
|
||||
{
|
||||
flush();
|
||||
}
|
||||
JSON_CATCH(...)
|
||||
{
|
||||
// nothing can be done about an adapter that throws here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
buffer[used++] = c;
|
||||
oa->write_character(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
write_large(s, length);
|
||||
return;
|
||||
}
|
||||
if (length <= 32)
|
||||
{
|
||||
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||
used += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||
used += length;
|
||||
}
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
return buffer.data() + used;
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
{
|
||||
used += length;
|
||||
}
|
||||
|
||||
/// pass everything written so far on to the adapter
|
||||
void flush()
|
||||
{
|
||||
if (used != 0)
|
||||
{
|
||||
// reset first: the characters count as written even if the adapter throws
|
||||
const std::size_t length = used;
|
||||
used = 0;
|
||||
oa->write_characters(buffer.data(), length);
|
||||
}
|
||||
oa->write_characters(reserved.data(), length);
|
||||
}
|
||||
|
||||
private:
|
||||
/// a write that does not fit the space left in the buffer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_large(const CharType* s, const std::size_t length)
|
||||
{
|
||||
flush();
|
||||
if (length >= buffer_size)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||
used = length;
|
||||
}
|
||||
}
|
||||
|
||||
/// large enough for reserve() at any time
|
||||
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||
|
||||
output_adapter_t<CharType> oa;
|
||||
/// number of characters of @ref buffer not yet passed on
|
||||
std::size_t used = 0;
|
||||
std::array<CharType, buffer_size> buffer{};
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
@@ -71,7 +71,7 @@ class serializer
|
||||
|
||||
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
|
||||
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.
|
||||
*/
|
||||
serializer(output_adapter_protocol<char>& s, const char ichar,
|
||||
@@ -80,14 +80,11 @@ class serializer
|
||||
const std::size_t indent_step_ = 0,
|
||||
error_handler_t error_handler_ = error_handler_t::strict)
|
||||
: o(&s)
|
||||
, number_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, string_buffer() // NOLINT(readability-redundant-member-init)
|
||||
, indent_char(ichar)
|
||||
, pretty_print(pretty_print_)
|
||||
, ensure_ascii(ensure_ascii_)
|
||||
, indent_step(indent_step_)
|
||||
, error_handler(error_handler_)
|
||||
, write_buffer() // NOLINT(readability-redundant-member-init)
|
||||
{}
|
||||
|
||||
// deleted because of pointer members
|
||||
@@ -118,18 +115,18 @@ class serializer
|
||||
void dump(const BasicJsonType& val,
|
||||
const std::size_t current_indent = 0)
|
||||
{
|
||||
dump_element(val, current_indent, 0);
|
||||
dump_internal(val, current_indent);
|
||||
flush();
|
||||
}
|
||||
|
||||
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
|
||||
a virtual call per token; the public @ref dump flushes the buffer once the
|
||||
top-level value has been serialized. Scalars are written by @ref dump_scalar,
|
||||
which @ref dump_element calls without entering this function.
|
||||
Identical in behavior to the historical @ref dump, but writes into the
|
||||
serializer's internal @ref write_buffer instead of issuing a virtual call
|
||||
per token. The public @ref dump wraps this and flushes the buffer once the
|
||||
top-level value has been serialized.
|
||||
|
||||
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
|
||||
@@ -140,20 +137,20 @@ class serializer
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
void dump_container(const BasicJsonType& val,
|
||||
const std::size_t current_indent,
|
||||
const std::size_t depth)
|
||||
void dump_internal(const BasicJsonType& val,
|
||||
const std::size_t current_indent = 0,
|
||||
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()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (val.m_data.m_type == value_t::object)
|
||||
{
|
||||
if (val.m_data.m_value.object->empty())
|
||||
{
|
||||
put_literal("{}");
|
||||
@@ -162,27 +159,34 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("{\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("{\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
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)
|
||||
{
|
||||
put_object_key(i->first, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
put_indent(new_indent);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\": ");
|
||||
dump_internal(i->second, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(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, "\": ");
|
||||
dump_element(i->second, new_indent, depth + 1);
|
||||
put_indent(new_indent);
|
||||
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('}');
|
||||
}
|
||||
else
|
||||
@@ -193,19 +197,32 @@ class serializer
|
||||
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)
|
||||
{
|
||||
put_object_key(i->first, "\":");
|
||||
dump_element(i->second, 0, depth + 1);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
put_char(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
JSON_ASSERT(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, "\":");
|
||||
dump_element(i->second, 0, depth + 1);
|
||||
put_char('"');
|
||||
dump_escaped(i->first);
|
||||
put_literal("\":");
|
||||
dump_internal(i->second, current_indent, depth + 1);
|
||||
|
||||
put_char('}');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -217,24 +234,27 @@ class serializer
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal("[\n");
|
||||
|
||||
// variable to hold indentation for recursive calls
|
||||
const auto new_indent = next_indent(current_indent, indent_step);
|
||||
|
||||
put_literal_indent("[\n", new_indent);
|
||||
|
||||
// first n-1 elements
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||
{
|
||||
dump_element(*i, new_indent, depth + 1);
|
||||
put_literal_indent(",\n", new_indent);
|
||||
put_indent(new_indent);
|
||||
dump_internal(*i, new_indent, depth + 1);
|
||||
put_literal(",\n");
|
||||
}
|
||||
|
||||
// last element
|
||||
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(']');
|
||||
}
|
||||
else
|
||||
@@ -245,44 +265,39 @@ class serializer
|
||||
for (auto i = val.m_data.m_value.array->cbegin();
|
||||
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(',');
|
||||
}
|
||||
|
||||
// last element
|
||||
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(']');
|
||||
}
|
||||
|
||||
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:
|
||||
/*!
|
||||
@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
|
||||
|
||||
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
|
||||
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
|
||||
@@ -310,7 +325,8 @@ class serializer
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal_indent("\n", frame.current_indent);
|
||||
put_char('\n');
|
||||
put_indent(frame.current_indent);
|
||||
}
|
||||
|
||||
put_char('}');
|
||||
@@ -337,13 +353,16 @@ class serializer
|
||||
put_indent(frame.child_indent);
|
||||
}
|
||||
|
||||
put_char('"');
|
||||
dump_escaped(frame.object_it->first);
|
||||
|
||||
if (pretty_print)
|
||||
{
|
||||
put_object_key(frame.object_it->first, "\": ");
|
||||
put_literal("\": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
put_object_key(frame.object_it->first, "\":");
|
||||
put_literal("\":");
|
||||
}
|
||||
|
||||
const BasicJsonType& element = frame.object_it->second;
|
||||
@@ -362,7 +381,8 @@ class serializer
|
||||
{
|
||||
if (pretty_print)
|
||||
{
|
||||
put_literal_indent("\n", frame.current_indent);
|
||||
put_char('\n');
|
||||
put_indent(frame.current_indent);
|
||||
}
|
||||
|
||||
put_char(']');
|
||||
@@ -503,7 +523,7 @@ class serializer
|
||||
/*!
|
||||
@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
|
||||
in full: it looks like an object, but has no elements to descend into.
|
||||
|
||||
@@ -517,13 +537,78 @@ class serializer
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -542,19 +627,19 @@ class serializer
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -687,220 +678,6 @@ class serializer
|
||||
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:
|
||||
/*!
|
||||
@brief dump escaped string
|
||||
@@ -913,23 +690,14 @@ class serializer
|
||||
@param[in] s the string to escape
|
||||
|
||||
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 -
|
||||
// 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,
|
||||
// whose setup costs more than such a string takes to scan. The same
|
||||
// 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)
|
||||
{
|
||||
const auto& copyable = ascii_copyable();
|
||||
@@ -951,7 +719,6 @@ class serializer
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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
|
||||
@@ -1029,7 +796,7 @@ class serializer
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
put_run(s, i, run);
|
||||
put_string(s, i, i + run);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
i += run;
|
||||
@@ -1069,27 +836,6 @@ class serializer
|
||||
|
||||
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))
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
@@ -1367,12 +1113,6 @@ class serializer
|
||||
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();
|
||||
|
||||
// fill whatever room is left in the buffer; this is the whole job
|
||||
@@ -1406,51 +1146,6 @@ class serializer
|
||||
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
|
||||
|
||||
@@ -1475,28 +1170,6 @@ class serializer
|
||||
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)
|
||||
|
||||
@@ -1739,78 +1412,6 @@ class serializer
|
||||
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
|
||||
*
|
||||
@@ -2168,22 +1769,11 @@ class serializer
|
||||
/// the output of the serializer (non-owning; the adapter lives at the call site)
|
||||
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
|
||||
uninitialized_buffer<64> number_buffer;
|
||||
std::array<char, 64> number_buffer{{}};
|
||||
|
||||
/// string buffer
|
||||
uninitialized_buffer<512> string_buffer;
|
||||
std::array<char, 512> string_buffer{{}};
|
||||
|
||||
/// the indentation character
|
||||
const char indent_char;
|
||||
@@ -2203,7 +1793,7 @@ class serializer
|
||||
/// buffer collecting output before it is flushed to the output adapter, so
|
||||
/// that the many small structural writes become few bulk writes
|
||||
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
|
||||
std::size_t write_buffer_pos = 0;
|
||||
};
|
||||
|
||||
+50
-212
@@ -19,7 +19,6 @@
|
||||
#define INCLUDE_NLOHMANN_JSON_HPP_
|
||||
|
||||
#include <algorithm> // all_of, find, for_each, none_of
|
||||
#include <array> // array
|
||||
#include <cmath> // isnan
|
||||
#include <cstddef> // nullptr_t, ptrdiff_t, size_t
|
||||
#include <cstdint> // uint8_t
|
||||
@@ -6322,7 +6321,7 @@ public:
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6332,9 +6331,7 @@ public:
|
||||
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())
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_cbor(j);
|
||||
}
|
||||
|
||||
/// @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,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_cbor(j);
|
||||
writer.flush();
|
||||
binary_writer<char>(o, error_handler).write_cbor(j);
|
||||
}
|
||||
|
||||
/// @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)
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6363,9 +6358,7 @@ public:
|
||||
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)
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_msgpack(j);
|
||||
}
|
||||
|
||||
/// @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,
|
||||
const error_handler_t error_handler = error_handler_t::keep)
|
||||
{
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_msgpack(j);
|
||||
writer.flush();
|
||||
binary_writer<char>(o, error_handler).write_msgpack(j);
|
||||
}
|
||||
|
||||
/// @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())
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6397,9 +6388,7 @@ public:
|
||||
const bool use_size = false, const bool use_type = false,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
}
|
||||
|
||||
/// @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 error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type);
|
||||
writer.flush();
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type);
|
||||
}
|
||||
|
||||
/// @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())
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6434,9 +6421,7 @@ public:
|
||||
const bjdata_version_t version = bjdata_version_t::draft2,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
}
|
||||
|
||||
/// @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 error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_ubjson(j, use_size, use_type, true, true, version);
|
||||
writer.flush();
|
||||
binary_writer<char>(o, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
|
||||
}
|
||||
|
||||
/// @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())
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6467,9 +6450,7 @@ public:
|
||||
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())
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o, error_handler).write_bson(j);
|
||||
}
|
||||
|
||||
/// @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,
|
||||
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
|
||||
{
|
||||
binary_writer<char> writer(o, error_handler);
|
||||
writer.write_bson(j);
|
||||
writer.flush();
|
||||
binary_writer<char>(o, error_handler).write_bson(j);
|
||||
}
|
||||
|
||||
/// @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)
|
||||
{
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
@@ -6496,18 +6475,14 @@ public:
|
||||
/// @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)
|
||||
{
|
||||
binary_writer<std::uint8_t> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
binary_writer<std::uint8_t>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a BON8 serialization of a given JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/to_bon8/
|
||||
static void to_bon8(const basic_json& j, detail::output_adapter<char> o)
|
||||
{
|
||||
binary_writer<char> writer(o);
|
||||
writer.write_bon8(j);
|
||||
writer.flush();
|
||||
binary_writer<char>(o).write_bon8(j);
|
||||
}
|
||||
|
||||
/// @brief create a JSON value from an input in CBOR format
|
||||
@@ -6903,72 +6878,6 @@ public:
|
||||
/// @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
|
||||
/// @sa https://json.nlohmann.me/api/basic_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
|
||||
// false, with nothing changed, for all others
|
||||
string_t token;
|
||||
typename json_pointer::template plain_steps<basic_json> steps;
|
||||
const auto try_plain = [&result, &token, &steps](patch_operations operation, const string_t& path, const patch_members & members, const basic_json & val)
|
||||
const string_t value_key = "value";
|
||||
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;
|
||||
}
|
||||
|
||||
// a missing "value" is reported below
|
||||
const basic_json* const value_ptr = members.value;
|
||||
const basic_json& value = value_ptr != nullptr ? *value_ptr : val;
|
||||
if (operation != patch_operations::remove && value_ptr == nullptr)
|
||||
const auto value_it = val.m_data.m_value.object->find(value_key);
|
||||
const basic_json& value = value_it != val.m_data.m_value.object->end() ? value_it->second : val;
|
||||
if (operation != patch_operations::remove && value_it == val.m_data.m_value.object->end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -7162,7 +7071,7 @@ public:
|
||||
{
|
||||
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)
|
||||
{
|
||||
return false;
|
||||
@@ -7173,7 +7082,7 @@ public:
|
||||
|
||||
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
|
||||
return target != nullptr && *target == value;
|
||||
}
|
||||
@@ -7193,17 +7102,13 @@ public:
|
||||
}
|
||||
--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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
token.clear();
|
||||
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)
|
||||
{
|
||||
@@ -7259,32 +7164,31 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// the name of the first member to check
|
||||
const string_t op_member = "op";
|
||||
|
||||
// iterate and apply the operations
|
||||
for (const auto& val : json_patch)
|
||||
{
|
||||
// wrapper to get a value for an operation
|
||||
const auto get_value = [&val](basic_json * found,
|
||||
const string_t& op,
|
||||
const char* member,
|
||||
const auto get_value = [&val](const string_t& op,
|
||||
const string_t& member,
|
||||
bool string_type) -> basic_json &
|
||||
{
|
||||
// find value
|
||||
auto it = val.m_data.m_value.object->find(member);
|
||||
|
||||
// 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
|
||||
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
|
||||
return *found;
|
||||
return it->second;
|
||||
};
|
||||
|
||||
// type check: every element of the array must be an object
|
||||
@@ -7293,30 +7197,26 @@ public:
|
||||
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
|
||||
const string_t& op = *get_value(members.op, op_member, "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& op = *get_value("op", "op", 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);
|
||||
|
||||
// the usual operations on a plain pointer are applied without
|
||||
// building the pointer's reference tokens; anything else (and
|
||||
// every error) is handled below
|
||||
if (try_plain(operation, path, members, val))
|
||||
if (try_plain(operation, path, val))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// the general operations may change any container
|
||||
steps.reset();
|
||||
json_pointer ptr(path);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case patch_operations::add:
|
||||
{
|
||||
operation_add(ptr, get_value(members.value, "add", "value", false));
|
||||
operation_add(ptr, get_value("add", "value", false));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -7329,13 +7229,13 @@ public:
|
||||
case patch_operations::replace:
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||
@@ -7358,7 +7258,7 @@ public:
|
||||
|
||||
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);
|
||||
|
||||
// The copy is functionally identical to an "add"
|
||||
@@ -7376,7 +7276,7 @@ public:
|
||||
{
|
||||
// check if "value" matches the one at "path"
|
||||
// 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&)
|
||||
{
|
||||
@@ -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
|
||||
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);
|
||||
auto& members = *entry.m_data.m_value.object;
|
||||
auto hint = members.end();
|
||||
json_pointer::add_flattened(members, hint, string_t("op"), string_t(op), detail::priority_tag<2> {});
|
||||
json_pointer::add_flattened(members, hint, string_t("path"), path, detail::priority_tag<2> {});
|
||||
entry["op"] = op;
|
||||
entry["path"] = path;
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -7501,62 +7396,6 @@ public:
|
||||
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
|
||||
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
|
||||
@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,
|
||||
const string_t& path, TargetValues& target_values,
|
||||
const string_t& path, std::vector<const basic_json*>& target_values,
|
||||
std::vector<const_iterator>& added)
|
||||
{
|
||||
// first pass: one lookup per source key
|
||||
@@ -7853,7 +7691,7 @@ public:
|
||||
|
||||
case value_t::object:
|
||||
{
|
||||
diff_target_values target_values;
|
||||
std::vector<const basic_json*> target_values;
|
||||
std::vector<const_iterator> added;
|
||||
if (diff_object_keys(result, source, target, path, target_values, added))
|
||||
{
|
||||
|
||||
Generated
+488
-3176
File diff suppressed because it is too large.
Load diff
@@ -492,278 +492,3 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// numbers around every boundary of the integer encodings, and floats of each
|
||||
// kind (exact in float, needing double, special values)
|
||||
std::vector<json> boundary_numbers()
|
||||
{
|
||||
std::vector<json> numbers;
|
||||
for (int k = 0; k < 64; ++k)
|
||||
{
|
||||
const std::uint64_t p = std::uint64_t{1} << k;
|
||||
for (const std::uint64_t d :
|
||||
{
|
||||
p - 1, p, p + 1
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(d);
|
||||
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
{
|
||||
numbers.emplace_back(static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d));
|
||||
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
|
||||
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
|
||||
for (const double f :
|
||||
{
|
||||
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
|
||||
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::quiet_NaN()
|
||||
})
|
||||
{
|
||||
numbers.emplace_back(f);
|
||||
}
|
||||
return numbers;
|
||||
}
|
||||
|
||||
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
|
||||
template<typename Single>
|
||||
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
|
||||
{
|
||||
std::vector<std::uint8_t> bytes;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
const auto one = single(numbers[i]);
|
||||
bytes.insert(bytes.end(), one.begin(), one.end());
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Runs of numbers in arrays are encoded straight into space reserved from the
|
||||
// sink, by code that is separate from the encoding of a single value. Both have
|
||||
// to agree on every number: an array is its head and the encodings of its
|
||||
// elements, one after the other.
|
||||
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
|
||||
{
|
||||
const std::vector<json> numbers = boundary_numbers();
|
||||
|
||||
// arrays of every length that a one-byte head covers, so that runs are as
|
||||
// long as, and longer and shorter than, what fits one reservation
|
||||
for (const std::size_t count :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 14u, 15u, 23u
|
||||
})
|
||||
{
|
||||
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
|
||||
{
|
||||
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
|
||||
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
|
||||
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||
|
||||
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
|
||||
auto elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_cbor(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_cbor(array) == expected);
|
||||
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
|
||||
if (count > 15)
|
||||
{
|
||||
expected.push_back(0);
|
||||
expected.push_back(static_cast<std::uint8_t>(count));
|
||||
}
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_msgpack(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
CHECK(json::to_msgpack(array) == expected);
|
||||
|
||||
for (const bool bjdata :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>('['));
|
||||
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
|
||||
{
|
||||
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
expected.push_back(static_cast<std::uint8_t>(']'));
|
||||
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
|
||||
}
|
||||
|
||||
// BON8 has no integers above the int64 range
|
||||
bool representable = true;
|
||||
for (std::size_t i = first; i < first + count; ++i)
|
||||
{
|
||||
representable = representable && bon8_representable(numbers[i]);
|
||||
}
|
||||
if (representable)
|
||||
{
|
||||
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
|
||||
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||
{
|
||||
return json::to_bon8(j);
|
||||
});
|
||||
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||
if (count > 4)
|
||||
{
|
||||
expected.push_back(0xFE);
|
||||
}
|
||||
CHECK(json::to_bon8(array) == expected);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// An array that is written in more than one reservation (more numbers than fit
|
||||
// one), and arrays of an index with more digits, round trip for every format.
|
||||
TEST_CASE("large arrays and many digits of indices round trip")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 12000; ++i)
|
||||
{
|
||||
switch (i % 4)
|
||||
{
|
||||
case 0:
|
||||
array.push_back(i);
|
||||
break;
|
||||
case 1:
|
||||
array.push_back(-i);
|
||||
break;
|
||||
case 2:
|
||||
array.push_back(i * 0.25);
|
||||
break;
|
||||
default:
|
||||
array.push_back(i % 8 == 3);
|
||||
break;
|
||||
}
|
||||
}
|
||||
json document = json::object();
|
||||
document["numbers"] = array;
|
||||
document["nested"] = json::array({array, "text", array});
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
// Strings and keys are written with their head in one reservation up to a
|
||||
// length; around that length, and with every kind of head, they must read back.
|
||||
TEST_CASE("strings of every head length round trip")
|
||||
{
|
||||
json document = json::object();
|
||||
json array = json::array();
|
||||
for (const std::size_t length :
|
||||
{
|
||||
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
|
||||
})
|
||||
{
|
||||
const std::string text(length, 'x');
|
||||
array.push_back(text);
|
||||
document[std::string(length, 'k')] = text;
|
||||
}
|
||||
document["array"] = array;
|
||||
|
||||
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// What a write that throws leaves behind in an output adapter is what was written
|
||||
// before it, whatever the adapter and however the adapter sink buffers. (Without
|
||||
// exceptions, the writes in CHECK_THROWS_AS do not run.)
|
||||
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||
{
|
||||
json array = json::array();
|
||||
for (int i = 0; i < 700; ++i)
|
||||
{
|
||||
array.push_back(i * 1000);
|
||||
array.push_back("text " + std::to_string(i));
|
||||
}
|
||||
array.push_back("bad \xFF byte");
|
||||
json document = json::object();
|
||||
document["first"] = array;
|
||||
document["second"] = 1;
|
||||
|
||||
const auto strict = json::error_handler_t::strict;
|
||||
|
||||
SECTION("cbor")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("msgpack")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("ubjson")
|
||||
{
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
|
||||
CHECK(!as_vector.empty());
|
||||
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
CHECK(as_stream.str() == as_string);
|
||||
}
|
||||
|
||||
SECTION("bson")
|
||||
{
|
||||
// the size pass finds the string before anything is written
|
||||
std::vector<std::uint8_t> as_vector;
|
||||
std::string as_string;
|
||||
std::ostringstream as_stream;
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
|
||||
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
|
||||
CHECK(as_vector.empty());
|
||||
CHECK(as_string.empty());
|
||||
CHECK(as_stream.str().empty());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -285,58 +285,6 @@ TEST_CASE("custom number types: integers beyond 64 bits")
|
||||
CHECK(wide_json::to_bjdata(wide_json::array({uint_big, uint_big}), true, true) == expected_bjdata_array);
|
||||
}
|
||||
|
||||
SECTION("integers in arrays and objects")
|
||||
{
|
||||
// runs of numbers and scalar object members are encoded by loops of
|
||||
// their own for the number types up to 64 bits; a wider number type
|
||||
// takes the generic path, which checks the range of every integer
|
||||
const wide_json run = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), int_big, make_int(5)});
|
||||
const wide_json run_negative = wide_json::array({make_int(1), make_int(-3), int_big_negative});
|
||||
const wide_json run_unsigned = wide_json::array({make_uint(1), make_uint(7), uint_big});
|
||||
const wide_json members = wide_json::object({{"a", make_int(1)}, {"b", wide_json(1.5)}, {"c", int_big}});
|
||||
|
||||
const std::vector<wide_json> failing = {run, run_negative, run_unsigned, members, wrap(run), wrap(members)};
|
||||
for (const auto& value : failing)
|
||||
{
|
||||
CHECK_THROWS_AS(_ = wide_json::to_cbor(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_msgpack(value), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bson(wrap(value)), out_of_range&);
|
||||
CHECK_THROWS_AS(_ = wide_json::to_bon8(value), out_of_range&);
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// a failing write leaves no more than the elements before the failing one
|
||||
// (without exceptions, the writes in CHECK_THROWS_AS do not run)
|
||||
std::vector<std::uint8_t> out;
|
||||
CHECK_THROWS_AS(wide_json::to_cbor(run, out), out_of_range&);
|
||||
// (the head of the array, 1, -3, 7, and 1.5 as a single-precision float)
|
||||
CHECK(out == std::vector<std::uint8_t>({0x86, 0x01, 0x22, 0x07, 0xFA, 0x3F, 0xC0, 0x00, 0x00}));
|
||||
out.clear();
|
||||
CHECK_THROWS_AS(wide_json::to_bon8(run, out), out_of_range&);
|
||||
CHECK(!out.empty());
|
||||
#endif
|
||||
|
||||
// the same values that fit are written exactly as with 64-bit number types
|
||||
const wide_json fits = wide_json::array({make_int(1), make_int(-3), make_uint(7), wide_json(1.5), make_int(min_int64), make_int(max_int64), make_int(200), make_int(-70000)});
|
||||
const nlohmann::json fits64 = nlohmann::json::array({1, -3, 7, 1.5, (std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(), 200, -70000});
|
||||
CHECK(wide_json::to_cbor(fits) == nlohmann::json::to_cbor(fits64));
|
||||
CHECK(wide_json::to_msgpack(fits) == nlohmann::json::to_msgpack(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits) == nlohmann::json::to_ubjson(fits64));
|
||||
CHECK(wide_json::to_ubjson(fits, true, true) == nlohmann::json::to_ubjson(fits64, true, true));
|
||||
CHECK(wide_json::to_bjdata(fits) == nlohmann::json::to_bjdata(fits64));
|
||||
CHECK(wide_json::to_bjdata(fits, true, true) == nlohmann::json::to_bjdata(fits64, true, true));
|
||||
CHECK(wide_json::to_bon8(fits) == nlohmann::json::to_bon8(fits64));
|
||||
CHECK(wide_json::to_bson(wrap(fits)) == nlohmann::json::to_bson(nlohmann::json::object({{"v", fits64}})));
|
||||
|
||||
const wide_json fit_members = wide_json::object({{"a", make_int(1)}, {"b", make_int(-300)}, {"c", make_uint(max_uint64)}, {"d", wide_json(2.5)}});
|
||||
const nlohmann::json fit_members64 = nlohmann::json::object({{"a", 1}, {"b", -300}, {"c", (std::numeric_limits<std::uint64_t>::max)()}, {"d", 2.5}});
|
||||
CHECK(wide_json::to_cbor(fit_members) == nlohmann::json::to_cbor(fit_members64));
|
||||
CHECK(wide_json::to_msgpack(fit_members) == nlohmann::json::to_msgpack(fit_members64));
|
||||
CHECK(wide_json::to_ubjson(fit_members) == nlohmann::json::to_ubjson(fit_members64));
|
||||
CHECK(wide_json::to_bjdata(fit_members) == nlohmann::json::to_bjdata(fit_members64));
|
||||
CHECK(wide_json::to_bson(fit_members) == nlohmann::json::to_bson(fit_members64));
|
||||
}
|
||||
|
||||
SECTION("BJData ND-array")
|
||||
{
|
||||
// an ND-array element beyond 64 bits does not fit any dtype, so the
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,7 @@ TEST_CASE("detail::bit_cast")
|
||||
|
||||
// to and from byte arrays
|
||||
const auto bytes = bit_cast<std::array<std::uint8_t, 4>>(std::uint32_t{0x01020304u});
|
||||
// qualified: with a std::array argument, ADL would also find std::bit_cast in C++20
|
||||
CHECK(nlohmann::detail::bit_cast<std::uint32_t>(bytes) == 0x01020304u);
|
||||
CHECK(bit_cast<std::uint32_t>(bytes) == 0x01020304u);
|
||||
|
||||
static_assert(noexcept(bit_cast<std::uint64_t>(1.0)), "bit_cast must be noexcept");
|
||||
}
|
||||
|
||||
@@ -48,16 +48,14 @@ namespace
|
||||
class chunked_streambuf : public std::streambuf
|
||||
{
|
||||
public:
|
||||
// The first @a unbuffered characters are served without a get area, the
|
||||
// rest in get areas of @a chunk_size.
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size, std::size_t unbuffered = 0)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size), m_unbuffered(unbuffered)
|
||||
chunked_streambuf(std::string s, std::size_t chunk_size)
|
||||
: m_data(std::move(s)), m_chunk_size(chunk_size)
|
||||
{}
|
||||
|
||||
protected:
|
||||
int_type underflow() override
|
||||
{
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
if (m_chunk_size == 0)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos]) : traits_type::eof();
|
||||
}
|
||||
@@ -79,7 +77,7 @@ class chunked_streambuf : public std::streambuf
|
||||
|
||||
int_type uflow() override
|
||||
{
|
||||
if (m_chunk_size == 0 || m_pos < m_unbuffered)
|
||||
if (m_chunk_size == 0)
|
||||
{
|
||||
return m_pos < m_data.size() ? traits_type::to_int_type(m_data[m_pos++]) : traits_type::eof();
|
||||
}
|
||||
@@ -90,7 +88,6 @@ class chunked_streambuf : public std::streambuf
|
||||
std::string m_data;
|
||||
std::size_t m_pos = 0;
|
||||
std::size_t m_chunk_size;
|
||||
std::size_t m_unbuffered;
|
||||
};
|
||||
|
||||
// how a test reads from the stream
|
||||
@@ -104,9 +101,9 @@ enum class read_mode
|
||||
// Read @a input from a chunked_streambuf and describe everything observable:
|
||||
// the values or the exception, the stream state after each value, and the
|
||||
// characters left in the stream.
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode, std::size_t unbuffered = 0)
|
||||
std::string observe(const std::string& input, std::size_t chunk_size, read_mode mode)
|
||||
{
|
||||
chunked_streambuf buf(input, chunk_size, unbuffered);
|
||||
chunked_streambuf buf(input, chunk_size);
|
||||
std::istream is(&buf);
|
||||
std::string result;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
@@ -215,31 +212,6 @@ TEST_CASE("parsing from a std::istream scans the get area in bulk")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("results do not depend on a get area that appears late")
|
||||
{
|
||||
// the first characters come without a get area, then the streambuf
|
||||
// starts to use one: the token that is read at that point is complete
|
||||
for (const auto& input : corpus())
|
||||
{
|
||||
const std::string expected = observe(input, 0, read_mode::extract);
|
||||
for (const std::size_t unbuffered :
|
||||
{
|
||||
1u, 2u, 3u, 5u, 8u, 13u, 21u
|
||||
})
|
||||
{
|
||||
for (const std::size_t chunk_size :
|
||||
{
|
||||
1u, 2u, 3u, 7u, 64u
|
||||
})
|
||||
{
|
||||
CAPTURE(input)
|
||||
CAPTURE(unbuffered)
|
||||
CAPTURE(chunk_size)
|
||||
CHECK(observe(input, chunk_size, read_mode::extract, unbuffered) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(JSON_NOEXCEPTION)
|
||||
|
||||
SECTION("values are the same as when parsing from a string")
|
||||
|
||||
@@ -139,12 +139,9 @@ if __name__ == '__main__':
|
||||
|
||||
namespaces += [f'{namespaces[0]}::{ns}' for ns in inline_namespaces]
|
||||
|
||||
# The template renders a local XML file, not a web response, and autoescape is enabled.
|
||||
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
|
||||
env = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath=sys.path[0]), autoescape=True, trim_blocks=True,
|
||||
lstrip_blocks=True, keep_trailing_newline=True)
|
||||
template = env.get_template('nlohmann_json.natvis.j2')
|
||||
# nosemgrep: python.flask.security.xss.audit.direct-use-of-jinja2.direct-use-of-jinja2
|
||||
natvis = template.render(namespaces=namespaces)
|
||||
|
||||
output = args.output or os.path.join(args.repo_root, 'nlohmann_json.natvis')
|
||||
|
||||
Reference in new issue
Block a user