Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5da0e9181f | ||
|
|
5e13f834c1 | ||
|
|
e13e976801 | ||
|
|
898e6ee81d | ||
|
|
ef1cdb59ac | ||
|
|
0aff24ca37 | ||
|
|
c25aad3ca5 | ||
|
|
bd5227c2ec | ||
|
|
0cbe95ffa2 | ||
|
|
08d7fbb0a1 | ||
|
|
4155e2938f | ||
|
|
4971200b54 | ||
|
|
823eee3b59 | ||
|
|
3ff37ead75 | ||
|
|
4c3a3eb5b5 | ||
|
|
43e97b629b | ||
|
|
122d718330 | ||
|
|
a4974e72c1 | ||
|
|
6a0cf2ba2f | ||
|
|
a55cae45fd | ||
|
|
667e296707 | ||
|
|
ec2e50e26f | ||
|
|
4da932f518 | ||
|
|
a2248ed5d5 | ||
|
|
cc8334113c | ||
|
|
24de5b2f5c | ||
|
|
fb729028ba | ||
|
|
c9fdfc39db | ||
|
|
870bdec01f | ||
|
|
effa51d528 | ||
|
|
545477ffa8 | ||
|
|
aeba637593 | ||
|
|
466e56d92d | ||
|
|
b7b75aac8a | ||
|
|
516b9b516a | ||
|
|
1d2dea7769 | ||
|
|
f8ff0942b2 | ||
|
|
d1e6439187 | ||
|
|
dcf7e2222c | ||
|
|
d99932fb3f | ||
|
|
1d2cb49ff3 | ||
|
|
a2766df787 | ||
|
|
2ea5bed0e3 | ||
|
|
632f5369fe | ||
|
|
c2619bf460 | ||
|
|
351aeb7440 | ||
|
|
7b0af0072e | ||
|
|
650886d126 | ||
|
|
a759afc99f | ||
|
|
1143da4faa | ||
|
|
8de151f928 | ||
|
|
ab52c98f71 | ||
|
|
5e93415d91 | ||
|
|
7e8e8e219b | ||
|
|
5f1727cef2 | ||
|
|
c16dd7e4f5 | ||
|
|
792853d725 | ||
|
|
4bdf1b7e74 | ||
|
|
b1e9d98e41 | ||
|
|
f855d257df | ||
|
|
3e683e9c04 | ||
|
|
d1d84ed9af | ||
|
|
de8529f99b | ||
|
|
677794f076 | ||
|
|
437a95cfdb | ||
|
|
c8735246d0 | ||
|
|
9adb510a0d |
No files matched your search
@@ -111,6 +111,8 @@ context which existing file needs to be extended, and only very few cases requir
|
||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||
Tests that call `sax_parse` go into `unit-sax_parse.cpp` rather than into a large test file: every call instantiates
|
||||
the parser and the binary reader that recover from errors, which grows a test file considerably.
|
||||
|
||||
#### Exceptions
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
path: pr/
|
||||
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
env:
|
||||
MAIN_DIR: ${{ github.workspace }}/main
|
||||
INCLUDE_DIR: ${{ github.workspace }}/main/single_include/nlohmann
|
||||
@@ -77,13 +77,12 @@ jobs:
|
||||
cd $MAIN_DIR
|
||||
|
||||
# Built from this PR's own tools/macro_builder/main.cpp, not a
|
||||
# develop checkout: unlike amalgamate.py and generate_natvis.py,
|
||||
# this tool has no other source of truth to check against (its
|
||||
# own README documents that it must reproduce macro_scope.hpp
|
||||
# byte for byte), so there is nothing to gain from checking out a
|
||||
# separate copy, and doing so would make this step fail on a PR
|
||||
# that adds support for a new dispatch table until that PR itself
|
||||
# merges to develop.
|
||||
# develop checkout: unlike amalgamate.py, this tool has no other
|
||||
# source of truth to check against (its own README documents that
|
||||
# it must reproduce macro_scope.hpp byte for byte), so there is
|
||||
# nothing to gain from checking out a separate copy, and doing so
|
||||
# would make this step fail on a PR that adds support for a new
|
||||
# dispatch table until that PR itself merges to develop.
|
||||
TMPDIR=$(mktemp -d ./macro_builder_check.XXXXXX)
|
||||
c++ -std=c++11 tools/macro_builder/main.cpp -o "$TMPDIR/macro_builder"
|
||||
"$TMPDIR/macro_builder" > "$TMPDIR/paste.hpp"
|
||||
@@ -146,14 +145,14 @@ jobs:
|
||||
- name: Regenerate nlohmann_json.natvis
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
# The template is what this check verifies the committed natvis
|
||||
# file against, so take it from this PR rather than from the
|
||||
# develop checkout: generate_natvis.py loads the template from its
|
||||
# own directory, and with develop's copy a PR that changes the
|
||||
# template could never pass. Like the macro_builder step above,
|
||||
# this only renders PR content in a job without credentials.
|
||||
cp tools/generate_natvis/nlohmann_json.natvis.j2 $NATVIS_TOOL_DIR/
|
||||
python3 $NATVIS_TOOL_DIR/generate_natvis.py $MAIN_DIR
|
||||
# The generator and its template are what this check verifies the
|
||||
# committed natvis file against (which namespaces it lists, and
|
||||
# what each entry looks like), so run this PR's copy rather than
|
||||
# the develop checkout's: with develop's, a PR that changes either
|
||||
# could never pass. generate_natvis.py loads the template from its
|
||||
# own directory. Like the macro_builder step above, this only runs
|
||||
# PR content in a job without credentials.
|
||||
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
||||
|
||||
- name: Build patch and check for differences
|
||||
id: diff
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
permissions:
|
||||
security-events: write
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
||||
uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
name: Scan
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc
|
||||
|
||||
ci_meson_install:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
cmake --build build-import-split
|
||||
|
||||
ci_infer:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
|
||||
ci_static_analysis_ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
@@ -175,7 +175,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_test_coverage:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -430,7 +430,7 @@ jobs:
|
||||
run: cmake --build build --target ci_nvhpc
|
||||
|
||||
ci_emscripten:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
@@ -450,7 +450,7 @@ jobs:
|
||||
run: cmake --build build
|
||||
|
||||
ci_test_documentation:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-26.04
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"_comment": "Used by the ci_infer CMake target (#5715 item 4b). fail-on-issue makes CI fail on Infer findings; disable-issue-type is a type-level baseline for the ~174 pre-existing findings (all PULSE_UNNECESSARY_COPY*/PULSE_RESOURCE_LEAK/PULSE_CONST_REFABLE, mostly in test code) triaged in run https://github.com/nlohmann/json/actions/runs/35829411620 on commit 1054b2097, so CI fails only on a NEW issue type. Remove an entry here once its findings have been fixed or explicitly accepted.",
|
||||
"_comment_pulse": "Pulse stops exploring paths after pulse-max-disjuncts (default 20). With the default, basic_json::replace_value() (destroy + assert_invariant) exceeds the limit, Pulse loses the stored type, and reports false NULLPTR_DEREFERENCE findings for get_ptr() results in tests/src/unit-pointer_access.cpp.",
|
||||
"_comment_view": "Infer's model of std::unique_ptr mixes up values that are returned by value and own one: basic_json_document (parse() results) and the `built` values of the build() helper in tests/src/unit-json_view_builder.cpp. It reports USE_AFTER_DELETE for one of them after the destructor of an unrelated object (e.g. ~built() on line 345 invalidating `shallow` on line 350 of that file, or two documents parsed in one lambda in tests/src/unit-json_view_dump.cpp). AddressSanitizer and Valgrind are clean on the same tests, so reports for the json_view test files are excluded; findings reported in the library headers are still reported.",
|
||||
"pulse-max-disjuncts": 40,
|
||||
"fail-on-issue": true,
|
||||
"disable-issue-type": [
|
||||
@@ -10,5 +11,8 @@
|
||||
"PULSE_UNNECESSARY_COPY_OPTIONAL",
|
||||
"PULSE_RESOURCE_LEAK",
|
||||
"PULSE_CONST_REFABLE"
|
||||
],
|
||||
"report-block-list-path-regex": [
|
||||
"tests/src/unit-json_view.*\\.cpp"
|
||||
]
|
||||
}
|
||||
@@ -495,7 +495,7 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const detail::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning `true` [recovers from the error](https://json.nlohmann.me/features/parsing/error_recovery/): the parser repairs the input and continues.
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
@@ -503,7 +503,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `bool json::sax_parse(input, &my_sax)`; where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `bool` indicating the result of the last executed SAX event. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
Note the `sax_parse` function only returns a `bool` indicating whether the input was parsed without errors and no SAX event returned `false`. It does not return a `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error -- it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file [`json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp).
|
||||
|
||||
### STL-like access
|
||||
|
||||
|
||||
+3
-3
@@ -361,10 +361,10 @@ add_custom_target(ci_test_coverage
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||
|
||||
COMMENT "Compile and test with coverage"
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
cppcheck==1.5.2
|
||||
cppcheck==1.5.3
|
||||
@@ -132,6 +132,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -143,6 +147,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`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)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -93,7 +93,8 @@ Logarithmic in the size of the JSON object.
|
||||
|
||||
- [count](count.md) returns the number of occurrences of a key
|
||||
- [contains](contains.md) checks whether a key exists
|
||||
- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view
|
||||
- [basic_json_view::find](../basic_json_view/find.md) - the same lookup on a zero-copy view (a duplicate key resolves
|
||||
to the first member, whereas `parse()` keeps the last)
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ Constant.
|
||||
## See also
|
||||
|
||||
- [back](back.md) to access the last element
|
||||
- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view
|
||||
- [basic_json_view::front](../basic_json_view/front.md) - the same access on a zero-copy view (document order, not
|
||||
sorted; duplicate keys are visited)
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ When iterating over an array, `key()` will return the index of the element as st
|
||||
- [begin](begin.md) returns an iterator to the first element
|
||||
- [end](end.md) returns an iterator to one past the last element
|
||||
- [basic_json_view::items](../basic_json_view/items.md) - the same range on a zero-copy view (`#!cpp const auto`,
|
||||
not `#!cpp const auto&`: items are produced on the fly)
|
||||
not `#!cpp const auto&`: items are produced on the fly; document order, not sorted; duplicate keys are visited)
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -275,8 +275,9 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
- documentation on [runtime assertions](../../features/assertions.md)
|
||||
- see [`at`](at.md) for access by reference with range checking
|
||||
- see [`value`](value.md) for access with default value
|
||||
- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (always
|
||||
returns a discarded view instead of assuming undefined behavior)
|
||||
- [basic_json_view::operator[]](../basic_json_view/operator%5B%5D.md) - the same access on a zero-copy view (a duplicate
|
||||
key resolves to the first member, whereas `parse()` keeps the last; a missing key gives a discarded view instead of
|
||||
undefined behavior)
|
||||
|
||||
## Version history
|
||||
|
||||
|
||||
@@ -112,6 +112,16 @@ 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 "Performance"
|
||||
|
||||
Contiguous inputs (strings, string views, vectors, arrays, pointer ranges) are the fastest to parse. A
|
||||
`#!cpp std::istream` comes close: the parser scans the characters its stream buffer holds in bulk, without reading
|
||||
ahead, so the stream is left exactly where it would be after reading one character at a time. A `#!cpp FILE*` is
|
||||
read one character at a time, but the file is locked only once, so other threads that use the same `FILE` object
|
||||
wait until parsing is done. See [input sources and performance](../../features/parsing/index.md#input-sources-and-performance),
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md), and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: (1) parse from a character array"
|
||||
@@ -247,6 +257,10 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -261,6 +275,11 @@ outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-by
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- Extended empty-input detection to also cover an `std::istream` without a stream buffer, and fixed a crash
|
||||
(`std::terminate`) when parsing from an `std::istream` with `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) restores reading one character at a
|
||||
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`).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -96,7 +96,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
|
||||
## Return value
|
||||
|
||||
return value of the last processed SAX event
|
||||
`#!cpp true` if the input was parsed without errors and no SAX event returned `#!cpp false`; `#!cpp false` otherwise.
|
||||
In particular, the result is `#!cpp false` for input with errors, even if the SAX parser recovered from all of them
|
||||
(see [error recovery](../../features/parsing/error_recovery.md)).
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -116,6 +118,10 @@ consumer `sax` has a super-linear complexity.
|
||||
|
||||
A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
When reading from a `#!c FILE*`, the file is locked while it is parsed. The lock is recursive, so the SAX handler may
|
||||
use the same file with stdio functions, but it must not wait for another thread that uses the same `FILE` object; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -137,6 +143,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../macros/json_disable_stream_bulk_scan.md) - read a `std::istream` one character
|
||||
at a time
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../macros/json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -145,9 +155,14 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Added `tag_handler` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Recovering from parse errors (see [`parse_error`](../json_sax/parse_error.md)) added in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
- Parsing from an `std::istream` scans the characters in its stream buffer in bulk in version 3.13.0
|
||||
([`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)).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -123,4 +123,6 @@ Linear in the size of the JSON value `j`.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
array or object was silently skipped, producing invalid BJData.
|
||||
- Writes unsigned integers wider than 64 bits as high-precision numbers since version 3.13.0; previously, they were
|
||||
silently truncated to 64 bits.
|
||||
@@ -37,8 +37,9 @@ With (2), the bytes written before the exception remain in the output adapter.
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an unsigned integer
|
||||
above 9223372036854775807, which BON8 cannot represent
|
||||
- Throws [out_of_range.407](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer outside
|
||||
the range of int64 (an unsigned integer above 9223372036854775807, or, with a number type wider than 64 bits, any
|
||||
integer beyond int64), which BON8 cannot represent
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if `j` contains a string that is not
|
||||
valid UTF-8
|
||||
- Throws [type_error.321](../../home/exceptions.md#jsonexceptiontype_error321) if `j` or a value nested in it is
|
||||
|
||||
@@ -47,6 +47,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
- Throws [`type_error.317`](../../home/exceptions.md#jsonexceptiontype_error317) if the top-level type of the JSON value
|
||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains a signed integer
|
||||
outside the range of int64 or an unsigned integer outside the range of uint64, which is only possible with a number
|
||||
type wider than 64 bits; example:
|
||||
`"integer number 9223372036854775808 cannot be represented by BSON as it does not fit int64"`
|
||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
||||
@@ -119,3 +123,5 @@ pass before anything is written.
|
||||
that is not valid UTF-8 unchanged, as before; `strict` (the default if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316` before anything
|
||||
is written.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^64, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]"`
|
||||
- Throws [type_error.316](../../home/exceptions.md#jsonexceptiontype_error316) if a string or object key in `j` is
|
||||
not valid UTF-8 and `error_handler` is `strict` (the default only if
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled)
|
||||
@@ -90,3 +93,5 @@ Linear in the size of the JSON value `j`.
|
||||
[`JSON_STRICT_BINARY_UTF8`](../macros/json_strict_binary_utf8.md) is enabled) throws `type_error.316`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid CBOR.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -46,6 +46,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Exceptions
|
||||
|
||||
- Throws [`out_of_range.407`](../../home/exceptions.md#jsonexceptionout_of_range407) if `j` contains an integer
|
||||
outside [-2^63, 2^64-1], which is only possible with a number type wider than 64 bits; example:
|
||||
`"integer number 18446744073709551616 cannot be represented by MessagePack as it does not fit [-2^63, 2^64-1]"`
|
||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a string, binary
|
||||
value, array, or object exceeds 4294967295, the maximum MessagePack can store; example:
|
||||
`"MessagePack length 4294967296 exceeds maximum of 4294967295"`
|
||||
@@ -112,3 +115,5 @@ Linear in the size of the JSON value `j`.
|
||||
`number_unsigned_t`.
|
||||
- Throws `type_error.321` for a discarded value since version 3.13.0; previously, a discarded value nested in an
|
||||
array or object was silently skipped, producing invalid MessagePack.
|
||||
- Throws `out_of_range.407` for integers that do not fit 64 bits since version 3.13.0; previously, integers of a
|
||||
number type wider than 64 bits were silently truncated.
|
||||
@@ -18,10 +18,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
1. Removes every member of `object` whose key is `key` (see [Notes](#notes) on duplicate keys) and returns how many
|
||||
were removed; `#!cpp 0` if `object` has no member with this key.
|
||||
2. Removes the element at index `idx` of `array`, which must already exist (`#!cpp idx < array.size()`).
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md), and returns how many values
|
||||
were removed: the *parent* of the target must already exist, and the target itself is removed as in 1. (an object
|
||||
member; `#!cpp 0` or more) or 2. (an array element; always `#!cpp 1`). `ptr` must not be empty -- [`root()`](root.md)
|
||||
itself cannot be erased.
|
||||
3. Removes the value the JSON pointer `ptr` refers to, relative to [`root()`](root.md). `ptr` must not be empty
|
||||
([`root()`](root.md) itself cannot be erased), and the *parent* of the target must already exist. The target is
|
||||
removed as in 1. if the parent is an object (returns the number of removed members, `#!cpp 0` or more) and as in 2.
|
||||
if it is an array (always returns `#!cpp 1`).
|
||||
|
||||
## Template parameters
|
||||
|
||||
@@ -69,10 +69,10 @@ has `erase`; calling it on a read-only `basic_json_document` fails to compile (`
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) (an empty token); otherwise (an
|
||||
object, or a primitive value the pointer's parent resolves to) throws what 1. throws.
|
||||
|
||||
Every overload also throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) ("view
|
||||
does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md) view or a
|
||||
view of a *different* document (overloads 1-2 only; overload 3 always starts from this document's own
|
||||
[`root()`](root.md)).
|
||||
Overloads 1. and 2. also throw [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202)
|
||||
("view does not belong to this document") if `object`/`array` is a [discarded](../basic_json_view/is_discarded.md)
|
||||
view or a view of a *different* document. Overload 3. never throws `invalid_iterator.202`: it takes no view, and it
|
||||
always starts from this document's own [`root()`](root.md).
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -92,9 +92,10 @@ view of a *different* document (overloads 1-2 only; overload 3 always starts fro
|
||||
|
||||
Like [`set`](set.md) and [`push_back`](push_back.md), `erase` never moves an element's *value*: a view still
|
||||
referring to a removed member or element keeps showing what it last held (see [Edits](index.md#edits)) -- it just no
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. Removing an element of `array` (2.) does shift the
|
||||
*links* to the elements after it, the same way `insert`, `set`, or `push_back` on the same array would; any iterator
|
||||
already taken over `array`/`object` is invalidated by an erase, since it was walking the old layout.
|
||||
longer appears when `array`/`object` is read, dumped, or iterated. What does change is the container's sequence of
|
||||
*links* to its elements: removing an element of `array` (2.) shifts the links to the elements after it by one, as
|
||||
[`insert`](insert.md) does in the other direction. *Every* iterator already taken over `array`/`object` is invalidated
|
||||
by an erase, not only those at or after the removed element, since the container may now use a different sequence.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -110,17 +110,30 @@ enum class image_check
|
||||
|
||||
How thoroughly `load()` validates `image` before trusting it.
|
||||
|
||||
| value | checks | guarantees |
|
||||
|----------|--------------------------------------------------------------------------------------------------------------|------------|
|
||||
| `full` | everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a string still in the source text, that it contains no quote, backslash, or control character); and that every number token is well-formed and matches the value stored for it; strings and float tokens may share a range only if the ranges are identical (as nodes that share a value do), and never overlap otherwise | reading and serializing a checked image is safe and always produces valid JSON, exactly as for a parsed document |
|
||||
| `bounds` | structure and bounds only -- that every offset and count in the node index stays inside the image | reading and serializing stay memory-safe, but a crafted image can hold strings that are not valid UTF-8 or that serialize to invalid JSON ([`dump()`](../basic_json_view/dump.md) writes them unchanged or throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316)), and numbers whose values differ from their text |
|
||||
| `none` | nothing | images from a trusted source only -- reading a damaged image is undefined behavior |
|
||||
`full` (the default)
|
||||
: Checks everything the parser itself guarantees: structure and bounds; that every string is valid UTF-8 (and, for a
|
||||
string still in the source text, that it contains no quote, backslash, or control character); and that every number
|
||||
token is well-formed and matches the value stored for it. Strings and float tokens may share a range only if the
|
||||
ranges are identical (as nodes that share a value do), and never overlap otherwise. Reading and serializing a
|
||||
checked image is safe and always produces valid JSON, exactly as for a parsed document. This is the right choice for
|
||||
an image from anything you do not fully control -- a file, a cache shared with other processes, a peer on the
|
||||
network.
|
||||
|
||||
`full` is the default and the right choice for an image from anything you do not fully control -- a file, a cache
|
||||
shared with other processes, a peer on the network. `bounds` skips scanning the text and the decoded strings, so it
|
||||
fits a cache your own process just wrote and reads straight back, where damage would mean a bug or a hardware fault
|
||||
rather than adversarial input; it still cannot crash or read out of bounds. `none` skips validation entirely and
|
||||
should only be used for an image you trust as much as your own memory.
|
||||
`bounds`
|
||||
: Checks structure and bounds only: that every offset and count in the node index stays inside the image. So
|
||||
`load()`, [`root()`](root.md), element access, and [`materialize()`](../basic_json_view/materialize.md) can never
|
||||
read outside the image, even on a damaged one. It does *not* guarantee that the image describes valid JSON: a
|
||||
string that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8
|
||||
or invalid JSON, or throw [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316), and a number can
|
||||
read back with a value that does not match how it is spelled. An [editable document](../json_editable_document.md)
|
||||
does not take such a string over: [`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw
|
||||
`type_error.316` when they copy it from a view of the loaded document, and leave the editable document unchanged.
|
||||
`bounds` skips scanning the text and the decoded strings, so it fits a cache your own process just wrote and reads
|
||||
straight back, where damage would mean a bug or a hardware fault rather than adversarial input.
|
||||
|
||||
`none`
|
||||
: Checks nothing. Reading a damaged image is undefined behavior, so use it only for an image you trust as much as
|
||||
your own memory.
|
||||
|
||||
**Lifetime.** Overloads 1 and 2 borrow `image`: it must stay alive and byte-for-byte unchanged for as long as the
|
||||
returned document, and any [view](../basic_json_view/index.md) taken from it, is used -- exactly like a document
|
||||
@@ -134,16 +147,6 @@ returned document, and any [view](../basic_json_view/index.md) taken from it, is
|
||||
("unknown format"). Use images to cache a document within one build of the library, or to hand one to another
|
||||
process running the *same* build on the *same* (little-endian) machine -- not as a long-term storage format.
|
||||
|
||||
**What `image_check::bounds` does not guarantee.** A bounds-checked image can never make `load()`,
|
||||
[`root()`](root.md), element access, or [`materialize()`](../basic_json_view/materialize.md) read outside the image,
|
||||
so those stay safe on a damaged one. It does *not* guarantee that the image describes valid JSON: a string
|
||||
that a `full` check would have rejected can make [`dump()`](../basic_json_view/dump.md) write invalid UTF-8 or invalid
|
||||
JSON, or throw `type_error.316`, and a number can read back with a value that does not match how it is spelled.
|
||||
Reserve `bounds` for images you already trust to be well-formed, and use it only to skip the extra scan.
|
||||
An [editable document](../json_editable_document.md) does not take such a string over either:
|
||||
[`set`](set.md), [`push_back`](push_back.md) and [`insert`](insert.md) throw `type_error.316` when they copy it from
|
||||
a view of the loaded document, and leave the editable document unchanged.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Caching a document, ownership, and a rejected image"
|
||||
|
||||
@@ -41,7 +41,7 @@ basic_json_view at(const json_pointer& ptr) const;
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -77,8 +77,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
|
||||
@@ -13,7 +13,7 @@ The last element or member value. For a primitive value (number, string, boolean
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -33,8 +33,7 @@ index.
|
||||
## Notes
|
||||
|
||||
Unlike [`BasicJsonType::back()`](../basic_json/back.md), which has undefined behavior for an empty array or object,
|
||||
`back()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and for a
|
||||
discarded view, where `BasicJsonType::back()` also throws.
|
||||
`back()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded view.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ iterator begin() const noexcept;
|
||||
|
||||
Returns an iterator to the first element of an array, or the first member value of an object, in **document order**
|
||||
-- the order the values appear in the source text, not sorted by key. A primitive value iterates as a range of one
|
||||
element (itself); `#!json null` and a [discarded](is_discarded.md) view iterate as an empty range.
|
||||
element (itself), and a `#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range.
|
||||
|
||||
## Return value
|
||||
|
||||
@@ -23,7 +23,7 @@ Constant.
|
||||
## Notes
|
||||
|
||||
For an object, iteration visits **every** member, including all occurrences of a duplicate key -- unlike
|
||||
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md), and [`count`](count.md),
|
||||
[`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), and [`contains`](contains.md),
|
||||
which all resolve to the *first* member with a given key. See the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
|
||||
|
||||
@@ -31,6 +31,10 @@ Because objects are iterated in document order rather than sorted by key, the or
|
||||
iterating the [`materialize()`](materialize.md)d `BasicJsonType` value would produce: a `#!cpp basic_json` object
|
||||
(`std::map`-backed by default) sorts its keys, while a view does not.
|
||||
|
||||
An `ordered_json` value also keeps document order, but it keeps only one entry per key: as `parse()` does, it keeps the
|
||||
*last* value, at the position of the key's *first* occurrence. The view, in contrast, visits every member, including
|
||||
the duplicates.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
@@ -35,8 +35,8 @@ No-throw guarantee: this function never throws exceptions.
|
||||
1. Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level or the index into the array -- as for [`operator[]`](operator[].md#complexity) and
|
||||
[`at`](at.md#complexity) with a JSON pointer.
|
||||
@@ -55,7 +55,8 @@ view.
|
||||
|
||||
!!! info "Postconditions"
|
||||
|
||||
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md). If
|
||||
If `#!cpp v.contains(key)` returns `#!cpp true`, then `#!cpp v[key]` is not [discarded](is_discarded.md) and
|
||||
`#!cpp v.at(key)` does not throw. If
|
||||
`#!cpp v.contains(ptr)` returns `#!cpp true`, then `#!cpp v[ptr]` is not discarded and `#!cpp v.at(ptr)` does not
|
||||
throw.
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ size_type count(const char* key) const;
|
||||
size_type count(const string_t& key) const;
|
||||
```
|
||||
|
||||
Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp 0` otherwise.
|
||||
Returns the number of members with key `key` if the value is an object, `#!cpp 0` otherwise. Members with a repeated
|
||||
key are all counted.
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -15,7 +16,8 @@ Returns `#!cpp 1` if the value is an object with a member with key `key`, `#!cpp
|
||||
|
||||
## Return value
|
||||
|
||||
`#!cpp 1` if the value is an object and has a member with key `key`, `#!cpp 0` otherwise.
|
||||
the number of members of the object with key `key` (duplicates included), or `#!cpp 0` if the value is not an object
|
||||
or has no such member.
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -23,36 +25,30 @@ No-throw guarantee: this function never throws exceptions.
|
||||
|
||||
## Complexity
|
||||
|
||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
|
||||
in document order, stopping at the first match. Each comparison first checks the key's length
|
||||
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||
average.
|
||||
Linear in the number of members: unlike the lookup functions, `count()` does not stop at the first match, because it
|
||||
has to count every member with the key. Members are compared one after another, in document order. Each comparison
|
||||
first checks the key's length -- already known from the index, without reading the key bytes -- before comparing its
|
||||
content. The hash index of the keys of objects with 128 or more members does not help here, since every member
|
||||
is visited.
|
||||
|
||||
## Notes
|
||||
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text repeats a key, this resolves to the *first* member with it, not to the last one that
|
||||
[`materialize()`](materialize.md) and `parse()` keep. See the [Notes on duplicate keys](operator[].md#notes) of
|
||||
`operator[]` and [Duplicate keys](../../features/json_view.md#duplicate-keys) for the reasons and for how to get
|
||||
the last value.
|
||||
|
||||
This method always returns `#!cpp 0` when the value is not an object -- including a [discarded](is_discarded.md)
|
||||
view.
|
||||
|
||||
Unlike [`BasicJsonType::count()`](../basic_json/count.md), whose return value can in principle exceed `#!cpp 1` for
|
||||
an `ObjectType` that allows multiple entries per key, `count()` here never does: it is exactly
|
||||
[`contains()`](contains.md) as `#!cpp 0`/`#!cpp 1`. This holds even if the source text has a duplicate key -- see the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]` -- because a `#!cpp count() > 1` result would require
|
||||
counting every member with a matching key (the lookup functions resolve to the *first* one).
|
||||
If the source text repeats a key, `count()` returns more than `#!cpp 1` for it, even though the
|
||||
[materialized](materialize.md) value (like the result of `parse()`) would have only one member with that key. Hence
|
||||
`#!cpp count(key) > 1` detects duplicate keys. This is unlike the lookup functions
|
||||
([`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md), [`contains`](contains.md)), which resolve to the
|
||||
*first* member; see the [Notes on duplicate keys](operator[].md#notes) of `operator[]`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The example below validates that every transaction of a batch carries a mandatory `amount` field, using
|
||||
`count()` before deciding whether to materialize a transaction at all.
|
||||
The example below validates that every transaction of a batch carries exactly one `amount` field, using
|
||||
`count()` before deciding whether to materialize a transaction at all. It reports a transaction with no `amount`
|
||||
field and one with a duplicated `amount` field.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/basic_json_view__count.cpp"
|
||||
|
||||
@@ -60,11 +60,13 @@ Linear in the size of the output text.
|
||||
The walk over the subtree is iterative, so the nesting depth it can write is limited by available memory only, not by
|
||||
the call stack -- as for [`materialize()`](materialize.md).
|
||||
|
||||
Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md). With
|
||||
`#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion,
|
||||
Strings are escaped by the same rules as [`BasicJsonType::dump`](../basic_json/dump.md).
|
||||
|
||||
With `#!cpp numbers == number_format::shortest`, floats are written with the library's shortest round-trip conversion,
|
||||
exactly as [`BasicJsonType::dump`](../basic_json/dump.md) would (e.g. `#!cpp 1.5`, `#!cpp 100.0`, `#!cpp 1e+100`), and
|
||||
integers are copied from the source text -- already canonical in JSON, so this matches their shortest form too --
|
||||
except that `#!cpp -0` is written as `#!cpp 0`, the way [`BasicJsonType::parse()`](../basic_json/parse.md) reads it.
|
||||
|
||||
`#!cpp number_format::source` copies every number exactly as written in the source text instead, with no exception
|
||||
for `#!cpp -0` -- `#!cpp 1.50`, `#!cpp 1E2`, `#!cpp -0.0`, `#!cpp -0`, or all digits of an integer literal with more
|
||||
digits than any number type holds (such a literal is itself classified as a float, see
|
||||
|
||||
@@ -5,9 +5,8 @@ iterator end() const noexcept;
|
||||
```
|
||||
|
||||
Returns an iterator to one past the last element of an array, one past the last member value of an object, in
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself);
|
||||
`#!json null` and a [discarded](is_discarded.md) view iterate as an empty range, so `#!cpp begin() == end()` for
|
||||
them.
|
||||
**document order** -- see [`begin()`](begin.md). A primitive value iterates as a range of one element (itself), and a
|
||||
`#!json null` value or a [discarded](is_discarded.md) view iterates as an empty range (`#!cpp begin() == end()`).
|
||||
|
||||
## Return value
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ No-throw guarantee: this function never throws exceptions.
|
||||
Linear in the number of members: as for [`ordered_json`](../ordered_json.md), members are compared one after another,
|
||||
in document order, stopping at the first match. Each comparison first checks the key's length
|
||||
-- already known from the index, without reading the key bytes -- before comparing its content.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time on
|
||||
average.
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ The first element or member value. For a primitive value (number, string, boolea
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -31,8 +31,8 @@ Constant.
|
||||
## Notes
|
||||
|
||||
Unlike [`BasicJsonType::front()`](../basic_json/front.md), which has undefined behavior for an empty array or
|
||||
object, `front()` throws `invalid_iterator.214` in that case -- the same way it already does for `#!json null` and
|
||||
for a discarded view, where `BasicJsonType::front()` also throws.
|
||||
object, `front()` throws `invalid_iterator.214` for an empty array, an empty object, a null value, or a discarded
|
||||
view.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ Converts the value to `T`.
|
||||
For the types below, the conversion works directly on the flat index -- no `BasicJsonType` value is built for it:
|
||||
|
||||
- `#!cpp bool`
|
||||
- arithmetic types other than `#!cpp bool` (from a number; from a boolean, as `#!cpp 0`/`#!cpp 1`, exactly as
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md) converts a boolean)
|
||||
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md))
|
||||
- `#!cpp std::nullptr_t`
|
||||
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) -- a copy of the string
|
||||
- [`string_view_t`](index.md#member-types) -- **no copy**: the returned view points into the document's
|
||||
@@ -19,10 +19,10 @@ For the types below, the conversion works directly on the flat index -- no `Basi
|
||||
document's own buffer of decoded strings (see [`get_string()`](get_string.md))
|
||||
- `BasicJsonType` -- equivalent to [`materialize()`](materialize.md)
|
||||
- `basic_json_view` -- returns `#!cpp *this`
|
||||
- `#!cpp std::vector<U, A>` -- element by element, each converted with `#!cpp get<U>()`; `#!cpp
|
||||
- `#!cpp std::vector<U, A>` -- from an array, element by element, each converted with `#!cpp get<U>()`; `#!cpp
|
||||
std::vector<basic_json_view>` keeps a view of every element instead of a value
|
||||
- `#!cpp std::map<K, V, C, A>` and `#!cpp std::unordered_map<K, V, H, E, A>`, if `K` is constructible from a `#!cpp
|
||||
(const char*, std::size_t)` pair -- member by member, each value converted with `#!cpp get<V>()`; with a repeated
|
||||
(const char*, std::size_t)` pair -- from an object, member by member, each value converted with `#!cpp get<V>()`; with a repeated
|
||||
key, the *last* value is kept, as [`BasicJsonType::parse()`](../basic_json/parse.md) (and
|
||||
[`materialize()`](materialize.md)) does; `#!cpp std::map<std::string, basic_json_view>` keeps views of the members
|
||||
instead of values
|
||||
@@ -43,7 +43,7 @@ the value, converted to `T`
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -62,6 +62,9 @@ None of the exceptions thrown directly by this function (the first three bullets
|
||||
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
||||
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
||||
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
||||
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
|
||||
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
|
||||
document.
|
||||
|
||||
## Complexity
|
||||
|
||||
@@ -73,8 +76,8 @@ exception thrown while converting through `materialize()` (the last bullet) is d
|
||||
- `#!cpp std::map`/`#!cpp std::unordered_map`: linear in the number of members for walking them, plus the container's
|
||||
own insertion cost per member (logarithmic for `#!cpp std::map`, amortized constant for `#!cpp
|
||||
std::unordered_map`), times the complexity of converting one member to the mapped type.
|
||||
- every other `T`: linear in the size of the subtree (building the `BasicJsonType` value), plus the complexity of
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md) on it.
|
||||
- every other `T`: the cost of [`materialize()`](materialize.md) and of destroying the `BasicJsonType` value
|
||||
afterwards, plus that of [`BasicJsonType::get<T>()`](../basic_json/get.md).
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ had to be decoded once when the document was parsed).
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -37,9 +37,14 @@ hand out a reference to a stored `string_t`. `get_string()` (equivalently, [`get
|
||||
zero-copy alternative: [`BasicJsonType::get_ref<const string_t&>()`](../basic_json/get_ref.md) is its closest
|
||||
counterpart, except that it returns a view instead of a reference to a value that must already exist.
|
||||
|
||||
`get_string()` borrows the string: nothing is copied, whereas [`get<std::string>()`](get.md) copies it. For a borrowed
|
||||
document, the returned [`string_view_t`](index.md#member-types) is valid as long as the caller's buffer; for an owned
|
||||
one, as long as the document.
|
||||
|
||||
The returned [`string_view_t`](index.md#member-types) is valid exactly as long as the view that produced it -- see the
|
||||
[validity rules](index.md) of `basic_json_view` -- and, for a string with no escapes, for as long as the document's
|
||||
source text.
|
||||
source text, which for a borrowed document must remain valid **and unmodified** for as long as the document exists,
|
||||
since floating-point values and unescaped strings are read from the source lazily.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -5,12 +5,15 @@ template<typename T>
|
||||
T& get_to(T& v) const;
|
||||
```
|
||||
|
||||
Converts the value to `T` and assigns it to `v`. Equivalent to
|
||||
Converts the value to `T` and stores it in `v`, in one of two ways:
|
||||
|
||||
```cpp
|
||||
v = get<T>();
|
||||
return v;
|
||||
```
|
||||
- For the types that [`get<T>()`](get.md) converts directly -- arithmetic types, `#!cpp bool`,
|
||||
`#!cpp std::nullptr_t`, [`string_view_t`](index.md#member-types), `#!cpp std::basic_string`, `BasicJsonType`,
|
||||
`basic_json_view`, `#!cpp std::vector`, and `#!cpp std::map`/`#!cpp std::unordered_map` with string keys -- `v` is
|
||||
assigned: `#!cpp v = get<T>()`.
|
||||
- For every other type, the subtree is built into a `BasicJsonType` value and `#!cpp materialize().get_to(v)` is
|
||||
called, i.e. `from_json()` is invoked with the existing `v`, exactly as
|
||||
[`BasicJsonType::get_to()`](../basic_json/get_to.md) does.
|
||||
|
||||
## Template parameters
|
||||
|
||||
@@ -28,15 +31,27 @@ return v;
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, `v` is not modified.
|
||||
- For the directly converted types: strong exception safety. If an exception is thrown, `v` is not modified.
|
||||
- For every other type: as for [`BasicJsonType::get_to()`](../basic_json/get_to.md). `materialize()` throws before `v`
|
||||
is touched, but a `from_json()` that throws may leave `v` partly modified.
|
||||
|
||||
## Exceptions
|
||||
|
||||
Whatever [`get<T>()`](get.md) throws for the same value and `T`.
|
||||
- For the directly converted types: whatever [`get<T>()`](get.md) throws for the same value and `T`.
|
||||
- For every other type: whatever [`materialize()`](materialize.md) and
|
||||
[`BasicJsonType::get_to()`](../basic_json/get_to.md) throw.
|
||||
|
||||
## Complexity
|
||||
|
||||
Whatever [`get<T>()`](get.md) has for the same `T`.
|
||||
- For the directly converted types: whatever [`get<T>()`](get.md) has for the same `T`.
|
||||
- For every other type: that of [`materialize()`](materialize.md), plus that of
|
||||
[`BasicJsonType::get_to()`](../basic_json/get_to.md).
|
||||
|
||||
## Notes
|
||||
|
||||
The difference between the two ways matters for a `from_json()` that sets only the fields present in the JSON: since
|
||||
it runs on the existing `v`, the other fields of `v` keep their values, whereas assigning `get<T>()` would replace
|
||||
them with those of a default-constructed `T`.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -14,18 +14,27 @@ to the document and a pointer into its index), trivially copyable. A view is val
|
||||
- the document has not been re-parsed with [`read()`](../basic_json_document/read.md) (or
|
||||
[`parse()`](../basic_json_document/parse.md) into it) or shrunk with
|
||||
[`shrink_to_fit()`](../basic_json_document/shrink_to_fit.md) since the view was taken, and
|
||||
- if the document borrows its source text, that text is still alive.
|
||||
- if the document borrows its source text, that text is still alive and unmodified (floating-point values and
|
||||
unescaped strings are read from it lazily).
|
||||
|
||||
Moving the document itself does not invalidate its views: the index is heap-allocated independently of the
|
||||
`basic_json_document` object.
|
||||
|
||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface: the type-inspection functions, element
|
||||
access, lookup, iteration, conversion, and comparison -- [`get<T>()`](get.md), [`get_string()`](get_string.md),
|
||||
[`number_token()`](number_token.md), and [`materialize()`](materialize.md) to build the `BasicJsonType` value of a
|
||||
subtree on demand. [`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and
|
||||
[`value`](value.md) also accept a [`json_pointer`](../json_pointer/index.md). [`operator==`](operator_eq.md) and
|
||||
[`operator!=`](operator_ne.md) compare two views, or a view and a `BasicJsonType` value, without ever building a
|
||||
`BasicJsonType` value for a view; no ordering comparison (`#!cpp operator<`) is provided.
|
||||
`basic_json_view` provides the read-only part of the `BasicJsonType` interface:
|
||||
|
||||
- the type-inspection functions
|
||||
- element access
|
||||
- lookup
|
||||
- iteration
|
||||
- conversion: [`get<T>()`](get.md), [`get_string()`](get_string.md), [`number_token()`](number_token.md), and
|
||||
[`materialize()`](materialize.md) to build the `BasicJsonType` value of a subtree on demand
|
||||
- comparison: [`operator==`](operator_eq.md) and [`operator!=`](operator_ne.md) compare two views, or a view and a
|
||||
`BasicJsonType` value, without ever building a `BasicJsonType` value for a view; no ordering comparison
|
||||
(`#!cpp operator<`) is provided
|
||||
|
||||
[`operator[]`](operator%5B%5D.md), [`at`](at.md), [`contains`](contains.md), and [`value`](value.md) also accept a
|
||||
[`json_pointer`](../json_pointer/index.md). Unlike `basic_json`, there are no implicit conversion operators (no
|
||||
`#!cpp operator ValueType()`), and no `#!cpp get_ptr()` or `#!cpp get_ref()`; see [`get`](get.md).
|
||||
|
||||
`basic_json_view` itself is always read-only -- it never has a `set` or `push_back` of its own. A view of an
|
||||
**editable** document (`#!cpp Editable == true`) sees every edit made through
|
||||
@@ -61,7 +70,6 @@ still refers to it -- including ones taken before the change -- reads the new va
|
||||
- **string_view_t** - `#!cpp std::string_view` on C++17 and newer, a minimal internal substitute otherwise
|
||||
- **iterator**, **const_iterator** - a forward iterator over the elements of an array or the member values of an
|
||||
object, in document order; both names refer to the same type, since a view is always read-only
|
||||
- **item** - a (key, value) pair produced by [`items()`](items.md)
|
||||
- [**number_format**](number_format.md) - how [`dump()`](dump.md) writes numbers
|
||||
|
||||
## Member functions
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
/* unspecified */ items() const noexcept;
|
||||
```
|
||||
|
||||
Returns a range of [`item`](index.md#member-types) values -- (key, value) pairs -- for use in range-based for loops.
|
||||
The key of an array element is its index, converted to a string, as for
|
||||
[`BasicJsonType::items()`](../basic_json/items.md).
|
||||
Returns a range of items -- (key, value) pairs -- for use in range-based for loops. An item's `#!cpp key()` is a
|
||||
`#!cpp string_view_t` (for an array element, its index converted to a string, as for
|
||||
[`BasicJsonType::items()`](../basic_json/items.md)), and its `#!cpp value()` is a `basic_json_view` of the element.
|
||||
|
||||
The returned type is not part of the public API and may change between versions; use a range-based for loop (see the
|
||||
example), or `#!cpp decltype(v.items())` if you need to name it.
|
||||
@@ -33,8 +33,8 @@ is nothing for a reference to bind to.
|
||||
|
||||
## Return value
|
||||
|
||||
A range whose iterators dereference to [`item`](index.md#member-types) and whose `#!cpp begin()`/`#!cpp end()` are
|
||||
equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order.
|
||||
A range whose iterators dereference to an item (with `#!cpp key()` and `#!cpp value()`, see above) and whose
|
||||
`#!cpp begin()`/`#!cpp end()` are equivalent to [`basic_json_view::begin()`](begin.md)/[`end()`](end.md), in document order.
|
||||
|
||||
## Exception safety
|
||||
|
||||
@@ -48,14 +48,14 @@ Constant.
|
||||
|
||||
As for [`begin()`](begin.md)/[`end()`](end.md), `items()` visits **every** member of an object, including all
|
||||
occurrences of a duplicate key -- unlike [`operator[]`](operator[].md), [`at`](at.md), [`find`](find.md),
|
||||
[`contains`](contains.md), and [`count`](count.md), which resolve to the *first* member with a given key. See the
|
||||
and [`contains`](contains.md), which resolve to the *first* member with a given key. See the
|
||||
[Notes on duplicate keys](operator[].md#notes) of `operator[]`.
|
||||
|
||||
!!! danger "Lifetime issues"
|
||||
|
||||
As for `BasicJsonType::items()`, calling `items()` on a temporary view (or a temporary document) is dangerous:
|
||||
the range refers back to the document, so the document must outlive the loop. See
|
||||
[#2040](https://github.com/nlohmann/json/issues/2040) for the `BasicJsonType` background.
|
||||
Unlike `BasicJsonType::items()` (see [#2040](https://github.com/nlohmann/json/issues/2040)), calling `items()` on
|
||||
a temporary view is safe, for instance `#!cpp for (auto item : v["a"].items())`: the returned range holds a copy
|
||||
of the view. What must outlive the loop is the document and, for a borrowed document, the source buffer.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ and [`materialize()`](materialize.md) can only represent approximately, as a `nu
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ basic_json_view operator[](const json_pointer& ptr) const;
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -83,8 +83,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
another, in document order, stopping at the first match. Each comparison first checks the
|
||||
key's length -- already known from the index, without reading the key bytes -- before comparing its content, so a
|
||||
key of a different length than `key` is rejected without touching the source text.
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in `idx`: elements are skipped one at a time from the first one, since they are not a fixed size in the
|
||||
index (unlike `BasicJsonType`'s array, which is random-access).
|
||||
3. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
@@ -110,13 +110,13 @@ document.
|
||||
!!! warning "Duplicate keys: the first member wins"
|
||||
|
||||
If the source text has an object with a duplicate key, `#!cpp operator[]` (and [`at`](at.md), [`find`](find.md),
|
||||
[`contains`](contains.md), [`count`](count.md), [`value`](value.md), and JSON pointer resolution) all resolve to
|
||||
[`contains`](contains.md), [`value`](value.md), and JSON pointer resolution) all resolve to
|
||||
the *first* member with that key, because a lookup can stop as soon as it finds a match. This is different from
|
||||
[`materialize()`](materialize.md) (and [`BasicJsonType::parse()`](../basic_json/parse.md)), which replay every
|
||||
member in order and so keep the *last* value for a repeated key, so `#!cpp v["a"]` and
|
||||
`#!cpp v.materialize()["a"]` can differ. To get the value `parse()` would give, use
|
||||
[`materialize()`](materialize.md) or iterate the members with [`items()`](items.md) and keep the last match.
|
||||
The hash index of a larger object (128 members or more) leads to the first member of a key as well.
|
||||
The key hash index of a larger object (128 members or more) leads to the first member of a key as well.
|
||||
[`begin()`](begin.md)/[`end()`](end.md) and [`items()`](items.md) iterate over *all* members, including
|
||||
duplicates, in document order. See [Duplicate keys](../../features/json_view.md#duplicate-keys) and
|
||||
[`size()`](size.md#notes).
|
||||
|
||||
@@ -14,14 +14,14 @@ same string for a discarded `BasicJsonType` value).
|
||||
a string representation of the type ([`value_t`](../basic_json/value_t.md)):
|
||||
|
||||
| Value type | return value |
|
||||
|-----------------------------------------------------|---------------|
|
||||
| `#!json null` | `"null"` |
|
||||
| boolean | `"boolean"` |
|
||||
| string | `"string"` |
|
||||
| number (integer, unsigned integer, floating-point) | `"number"` |
|
||||
| object | `"object"` |
|
||||
| array | `"array"` |
|
||||
| discarded | `"discarded"` |
|
||||
|----------------------------------------------------|---------------|
|
||||
| `#!json null` | `"null"` |
|
||||
| boolean | `"boolean"` |
|
||||
| string | `"string"` |
|
||||
| number (integer, unsigned integer, floating-point) | `"number"` |
|
||||
| object | `"object"` |
|
||||
| array | `"array"` |
|
||||
| discarded | `"discarded"` |
|
||||
|
||||
`type_name()` never returns `#!cpp "binary"`, since a JSON text has no binary values (see
|
||||
[`is_binary()`](is_binary.md)); it also never returns `#!cpp "invalid"`, since a view's `#!cpp kind` always comes
|
||||
|
||||
@@ -44,7 +44,7 @@ equivalent) deduce `string_t`, not `const char*`, for their return type and for
|
||||
|
||||
## Exception safety
|
||||
|
||||
Strong exception safety: if an exception is thrown, there are no changes to the view or the document it refers to.
|
||||
Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
|
||||
## Exceptions
|
||||
|
||||
@@ -70,8 +70,8 @@ None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics
|
||||
1. Linear in the number of members: as for [`operator[]`](operator[].md#complexity), members are compared one after
|
||||
another, in document order, stopping at the first match. Plus the complexity of converting
|
||||
the found member to `T` (see [`get`](get.md)).
|
||||
Objects with 128 or more members get a hash index while parsing, so that a lookup in them takes constant time
|
||||
on average.
|
||||
Objects with 128 or more members get a hash index of their keys while parsing, so that a lookup in them takes
|
||||
constant time on average.
|
||||
2. Linear in the number of reference tokens of `ptr` and, for each token, in the number of members of the object at
|
||||
that level or the index into the array -- as for the [`operator[]`](operator[].md#complexity) and
|
||||
[`at`](at.md#complexity) overloads that take a JSON pointer. Plus the complexity of converting the resolved value
|
||||
|
||||
@@ -7,7 +7,8 @@ struct json_sax;
|
||||
|
||||
This class describes the SAX interface used by [sax_parse](../basic_json/sax_parse.md). Each function is called in
|
||||
different situations while the input is parsed. The boolean return value informs the parser whether to continue
|
||||
processing the input.
|
||||
processing the input; for [`parse_error`](parse_error.md), it decides whether to
|
||||
[recover from the error](../../features/parsing/error_recovery.md).
|
||||
|
||||
For instance, parsing the JSON text `{"a": [1, true]}` triggers the following callbacks, in order:
|
||||
|
||||
|
||||
@@ -21,11 +21,18 @@ A parse error occurred.
|
||||
|
||||
## Return value
|
||||
|
||||
Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
Whether to recover from the error:
|
||||
|
||||
- `#!cpp false` stops parsing.
|
||||
- `#!cpp true` recovers from the error: the error is repaired and parsing continues. If that is not possible, which
|
||||
happens in the binary formats when the end of the item with the error is unknown, the value read so far is completed
|
||||
and parsing stops. See [error recovery](../../features/parsing/error_recovery.md) for how errors are repaired.
|
||||
|
||||
Either way, [`sax_parse`](../basic_json/sax_parse.md) returns `#!cpp false`.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
??? example "Example: (1) the SAX interface"
|
||||
|
||||
The example below shows how the SAX interface is used.
|
||||
|
||||
@@ -39,12 +46,29 @@ Whether parsing should proceed (**must return `#!cpp false`**).
|
||||
--8<-- "examples/sax_parse.output"
|
||||
```
|
||||
|
||||
??? example "Example: (2) recovering from errors"
|
||||
|
||||
The example below shows how a SAX parser recovers from errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [sax_parse](../basic_json/sax_parse.md) - SAX parser
|
||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - the article on handling parse errors without
|
||||
exceptions
|
||||
- [Error Recovery](../../features/parsing/error_recovery.md) - the article on recovering from parse errors
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.2.0.
|
||||
- Returning `#!cpp true` recovers from the error since version 3.13.0; before, parsing stopped, but the result of
|
||||
[`sax_parse`](../basic_json/sax_parse.md) could be wrong.
|
||||
@@ -16,6 +16,10 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
|
||||
## Parsing
|
||||
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a time
|
||||
instead of scanning its stream buffer in bulk
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with `std::fgetc` instead of
|
||||
locking it once and using the unlocked stdio functions
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
|
||||
right after a parsed number
|
||||
- [**JSON_STRICT_BINARY_UTF8**](json_strict_binary_utf8.md) - opt in to checking strings for valid UTF-8 in the CBOR,
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
# JSON_DISABLE_STREAM_BULK_SCAN
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads the input one character at a time through the stream
|
||||
buffer, as in version 3.12.0 and earlier, instead of scanning the characters that the stream buffer already holds in
|
||||
bulk.
|
||||
|
||||
The macro is an escape hatch for stream buffers that do not follow the usual contract of `std::basic_streambuf` (see
|
||||
the requirements on the stream buffer in the notes below). It does not change any result: values, exceptions, and the
|
||||
position of the stream after parsing are the same either way. Only the speed differs.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: streams are scanned in bulk.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "How streams are scanned"
|
||||
|
||||
A `std::streambuf` keeps the part of the input it has read but not yet handed out in its *get area*, the range
|
||||
from `gptr()` to `egptr()`. A `std::filebuf`, for instance, reads the file in blocks of a few kilobytes into its get
|
||||
area, and a `std::stringbuf` exposes its whole string as one get area.
|
||||
|
||||
When parsing from a `std::istream`, the parser scans this get area directly, with the same fast paths that it uses
|
||||
for a `std::string`: runs of ordinary string characters, numbers, and indentation are recognized several bytes at a
|
||||
time instead of being requested from the stream buffer one character at a time. When the parser reaches the end of
|
||||
the get area, it asks the stream buffer for the next character as before, which makes the stream buffer refill its
|
||||
get area.
|
||||
|
||||
The parser only reads characters that it would also have read one at a time, and it advances the stream buffer
|
||||
exactly over the characters it consumes. Nothing is read ahead, buffered elsewhere, or put back. Therefore, the
|
||||
stream is left exactly where it was left before:
|
||||
|
||||
- [`operator>>`](../operator_gtgt.md) and [`sax_parse`](../basic_json/sax_parse.md) with `strict = false` can still
|
||||
read several values from one stream, and the stream can still be used with other `std::istream` functions after a
|
||||
value has been read.
|
||||
- With [`JSON_PRECISE_STREAM_POSITION`](json_precise_stream_position.md), the stream is still positioned right after
|
||||
a parsed number.
|
||||
- Exception messages, including the line, the column, and the last read token, are unchanged.
|
||||
|
||||
!!! note "Performance"
|
||||
|
||||
Parsing from a `std::ifstream` or `std::istringstream` is typically 1.5 to 2 times as fast as before, and reading
|
||||
many small values from one stream with `operator>>` 1.4 to 4 times as fast. Parsing from a stream is then within
|
||||
about 10 to 20 % of first reading the whole file into a `std::string` and parsing that.
|
||||
|
||||
A stream buffer without a get area (one that only overrides `underflow()` and `uflow()` and hands out every
|
||||
character from there) offers nothing to scan in bulk, so it is read one character at a time as before.
|
||||
|
||||
!!! note "Requirements on the stream buffer"
|
||||
|
||||
The bulk scan relies on the following behavior, which every stream buffer of the standard library and every stream
|
||||
buffer that keeps its input in its get area has:
|
||||
|
||||
- When `sgetc()` returns a character while `gptr() < egptr()`, that character is `*gptr()`; when `sbumpc()` returns
|
||||
one, it was at `gptr()` before the call.
|
||||
- Characters in the get area are not changed while they are in the get area.
|
||||
- `underflow()` or `uflow()` either sets up a get area that holds the returned character at `gptr()` (or at
|
||||
`gptr() - 1` after `uflow()`), or leaves the get area empty.
|
||||
|
||||
A stream buffer that returns characters from `uflow()` while leaving unrelated characters in its get area, or that
|
||||
changes the characters in its get area in place, does not meet these requirements. Define the macro to `1` for
|
||||
such stream buffers.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dsbs`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Strings, string views, vectors, arrays, and pointer or iterator ranges over contiguous memory are scanned in bulk
|
||||
anyway. Reading from a `#!cpp FILE*` is not affected by this macro; see
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](json_disable_unlocked_file_io.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: reading values from a stream (default)"
|
||||
|
||||
The stream is scanned in bulk, and the values are read one after the other:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: reading one character at a time (macro defined to 1)"
|
||||
|
||||
With the macro, the same program reads the stream one character at a time, with the same results:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 1
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input(R"({"id": 1} {"id": 2} 42 rest)");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // {"id":1}, {"id":2}, 42
|
||||
|
||||
std::string word;
|
||||
input >> word; // "rest"
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - leave an input stream positioned right after a
|
||||
parsed number
|
||||
- [**JSON_DISABLE_UNLOCKED_FILE_IO**](json_disable_unlocked_file_io.md) - read a `FILE*` with the locking stdio
|
||||
functions
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -0,0 +1,115 @@
|
||||
# JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, as in version 3.12.0 and
|
||||
earlier, instead of locking the file once and reading it with the unlocked stdio functions.
|
||||
|
||||
It does not change any result: values, exceptions, and the position of the file after parsing are the same either way.
|
||||
Only the speed and the locking of the file differ (see the notes on locking below).
|
||||
|
||||
Builds with MemorySanitizer use `std::fgetc` automatically, because the inline `getc_unlocked` of glibc reads the stdio
|
||||
buffer directly, which glibc (not instrumented) filled, and MemorySanitizer would report it as uninitialized.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0`: the file is locked once and read with the unlocked stdio functions where they are available.
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
The C standard library makes every `std::fgetc` call thread-safe by locking and unlocking the `FILE` object. When
|
||||
parsing from a `FILE*`, the parser reads one character at a time, so this happens for every character of the input.
|
||||
|
||||
Instead, the parser locks the file once when parsing starts and unlocks it when parsing ends - also when an
|
||||
exception is thrown - and reads the characters in between without locking:
|
||||
|
||||
| C library | functions |
|
||||
|------------------------------------------------------------|----------------------------------------------------|
|
||||
| glibc (Linux), Apple (macOS, iOS), Android, FreeBSD, NetBSD, OpenBSD, DragonFly BSD | `flockfile`, `getc_unlocked`, `funlockfile` (POSIX) |
|
||||
| Microsoft (MSVC, clang-cl) | `_lock_file`, `_fgetc_nolock`, `_unlock_file` |
|
||||
| others (e.g., musl, MinGW, newlib) | `std::fgetc`, as before |
|
||||
|
||||
This makes parsing from a `FILE*` 3 to 4 times as fast with the Apple C library and 1.1 to 1.3 times as fast with
|
||||
glibc, whose locking is cheaper. The binary formats ([`from_cbor`](../basic_json/from_cbor.md),
|
||||
[`from_msgpack`](../basic_json/from_msgpack.md), ...) read from a `FILE*` the same way.
|
||||
|
||||
!!! note "Locking"
|
||||
|
||||
While a value is parsed from a `FILE*`, the parsing thread holds the file's lock. The lock is recursive, so the
|
||||
parsing thread itself - for instance, a [SAX](../json_sax/index.md) handler or a
|
||||
[parser callback](../basic_json/parser_callback_t.md) - can keep using the file with stdio functions such as
|
||||
`std::ftell`. Other threads that use the same `FILE` object wait until the parser is done. Before, they could
|
||||
interleave their reads with the parser's, which made the parsed input unpredictable anyway.
|
||||
|
||||
A program in which a SAX handler or parser callback waits for **another** thread that uses the **same** `FILE`
|
||||
object would deadlock. Define the macro to `1` for such programs.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_dufio`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Other inputs"
|
||||
|
||||
Reading from a `#!cpp std::istream` is not affected by this macro; see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md) for that input.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: parsing from a `FILE*` (default)"
|
||||
|
||||
The file is locked once while it is parsed:
|
||||
|
||||
```cpp
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // f is locked until parse() returns
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Example: locking for every character (macro defined to 1)"
|
||||
|
||||
With the macro, every character is read with `std::fgetc`, with the same result:
|
||||
|
||||
```cpp
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 1
|
||||
#include <cstdio>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::FILE* f = std::fopen("example.json", "rb");
|
||||
json j = json::parse(f); // std::fgetc for every character
|
||||
std::fclose(f);
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
- [**JSON_DISABLE_STREAM_BULK_SCAN**](json_disable_stream_bulk_scan.md) - read a `std::istream` one character at a
|
||||
time
|
||||
- [Parsing: input sources and performance](../../features/parsing/index.md#input-sources-and-performance)
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
@@ -72,6 +72,12 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! note "Bulk scanning"
|
||||
|
||||
The parser scans the characters in the stream buffer in bulk (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)), but it never reads ahead. The stream is left
|
||||
at the same position with and without bulk scanning, in both modes of this macro.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
||||
|
||||
@@ -12,8 +12,9 @@ non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
|
||||
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||
scalar path.
|
||||
diagnostic is preserved). A `std::istream` uses simdutf for the characters its stream buffer holds (see
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other streaming inputs (`FILE*`, wide strings,
|
||||
user-defined adapters) always use the scalar path.
|
||||
|
||||
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
|
||||
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||
|
||||
@@ -14,10 +14,12 @@ The same input is accepted or rejected either way, with the same values, and err
|
||||
the speed differs. The macro exists for platforms whose compilers lack the intrinsics headers, and to test the portable
|
||||
code.
|
||||
|
||||
!!! warning "Define consistently"
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The macro selects between two definitions of the same inline functions. It must therefore be defined identically for
|
||||
**every** translation unit that includes `<nlohmann/json_view.hpp>`; prefer a compile definition on the target.
|
||||
The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the
|
||||
[namespace](../../features/namespace.md) (tag `_vns`), resulting in distinct symbol names. Translation units
|
||||
compiled with and without it can therefore be linked into the same program without One Definition Rule (ODR)
|
||||
violations, but they cannot exchange instances of library types.
|
||||
|
||||
## Default definition
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ Define the macro only together with a compiler option that enables SSSE3 (e.g. `
|
||||
has it), and only for programs that run on such CPUs. It saves the check of the CPU, which costs little. The same
|
||||
input is accepted or rejected either way; only the speed of non-ASCII text differs.
|
||||
|
||||
!!! warning "Define consistently"
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The macro selects between two definitions of the same inline functions. It must therefore be defined identically,
|
||||
with the same compiler options, for **every** translation unit that includes `<nlohmann/json_view.hpp>`; mixing
|
||||
translation units that define it with ones that do not is an ODR violation. Prefer a compile definition on the
|
||||
target.
|
||||
The macro selects between two definitions of the same inline functions. Whether it is defined is encoded in the
|
||||
[namespace](../../features/namespace.md) (tag `_vssse3`, on every architecture), resulting in distinct symbol
|
||||
names. Translation units compiled with and without it can therefore be linked into the same program without One
|
||||
Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
## Default definition
|
||||
|
||||
|
||||
@@ -39,5 +39,6 @@ the library.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, and `_ekmo` in version 3.13.0; see
|
||||
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_vns`, and
|
||||
`_vssse3` in version 3.13.0; see
|
||||
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
||||
@@ -121,6 +121,7 @@ being read.
|
||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) - read the stream one character at a time
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -133,3 +134,6 @@ being read.
|
||||
crash (`std::terminate`) when `i` has `eofbit` in its exception mask, in version 3.13.0.
|
||||
- Changed to the strong exception safety guarantee in version 3.13.0: `j` is no longer left with a partially parsed
|
||||
value if parsing throws.
|
||||
- Scans the characters in the stream buffer of `i` in bulk in version 3.13.0; the position of `i` after the call is
|
||||
unchanged ([`JSON_DISABLE_STREAM_BULK_SCAN`](macros/json_disable_stream_bulk_scan.md) restores reading one character
|
||||
at a time).
|
||||
@@ -135,6 +135,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
headers are included.
|
||||
- [x] On Windows, the library is compiled with `<Windows.h>` being included to detect and avoid common bugs (see
|
||||
[`unit-windows_h.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-windows_h.cpp)).
|
||||
- [x] The library is compiled with Qt's keyword macros (`slots`, `signals`, `emit`, `foreach`, `forever`) being defined
|
||||
to make sure it does not use them as identifiers (see
|
||||
[`unit-qt_keywords.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-qt_keywords.cpp)).
|
||||
- [x] The library is compiled with exceptions disabled to support alternative means of error handling.
|
||||
|
||||
## Stable public API
|
||||
|
||||
@@ -5,13 +5,14 @@ using json_document = nlohmann::json_document;
|
||||
|
||||
int main()
|
||||
{
|
||||
// validate that every transaction of a batch carries a mandatory
|
||||
// validate that every transaction of a batch carries exactly one
|
||||
// "amount" field before materializing any of them into a nlohmann::json
|
||||
// value -- count() returns 0 or 1 for an object
|
||||
// value -- count() counts every member with the key
|
||||
json_document batch = json_document::parse(R"(
|
||||
[
|
||||
{"id": 1, "amount": 9.99},
|
||||
{"id": 2}
|
||||
{"id": 2},
|
||||
{"id": 3, "amount": 5.00, "amount": 500.00}
|
||||
]
|
||||
)");
|
||||
|
||||
@@ -19,9 +20,10 @@ int main()
|
||||
for (std::size_t i = 0; i < transactions.size(); ++i)
|
||||
{
|
||||
const auto transaction = transactions[i];
|
||||
if (transaction.count("amount") == 0)
|
||||
const auto amounts = transaction.count("amount");
|
||||
if (amounts != 1)
|
||||
{
|
||||
std::cout << "transaction " << i << " is missing \"amount\"\n";
|
||||
std::cout << "transaction " << i << " has " << amounts << " \"amount\" fields\n";
|
||||
continue;
|
||||
}
|
||||
std::cout << "transaction " << i << ": " << transaction["amount"].materialize().dump() << '\n';
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
transaction 0: 9.99
|
||||
transaction 1 is missing "amount"
|
||||
transaction 1 has 0 "amount" fields
|
||||
transaction 2 has 2 "amount" fields
|
||||
@@ -0,0 +1,43 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// a SAX parser that creates a JSON value like json::parse does, but that
|
||||
// recovers from parse errors instead of stopping at the first one
|
||||
class recovering_parser : public nlohmann::detail::json_sax_dom_parser<json>
|
||||
{
|
||||
public:
|
||||
explicit recovering_parser(json& result)
|
||||
: nlohmann::detail::json_sax_dom_parser<json>(result, false)
|
||||
{}
|
||||
|
||||
bool parse_error(std::size_t position,
|
||||
const std::string& /*last_token*/,
|
||||
const json::exception& ex)
|
||||
{
|
||||
std::cout << "byte " << position << ": " << ex.what() << '\n';
|
||||
|
||||
// repair the input and continue
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
// JSON text with several mistakes that ends too early
|
||||
const std::string text = R"({
|
||||
"name": "Hello World",
|
||||
"tags": ["a" "b",],
|
||||
"valid": tru,
|
||||
"size": 1.,
|
||||
"nested": {"x": 1)";
|
||||
|
||||
json result;
|
||||
recovering_parser sax(result);
|
||||
const bool valid = json::sax_parse(text, &sax);
|
||||
|
||||
std::cout << "\nvalid JSON: " << std::boolalpha << valid << '\n'
|
||||
<< std::setw(4) << result << std::endl;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
byte 49: [json.exception.parse_error.101] parse error at line 3, column 20: syntax error while parsing array - unexpected string literal; expected ']'
|
||||
byte 51: [json.exception.parse_error.101] parse error at line 3, column 22: syntax error while parsing value - unexpected ']'; expected '[', '{', or a literal
|
||||
byte 70: [json.exception.parse_error.101] parse error at line 4, column 17: syntax error while parsing value - invalid literal; last read: '"valid": tru,'
|
||||
byte 86: [json.exception.parse_error.101] parse error at line 5, column 15: syntax error while parsing value - invalid number; expected digit after '.'; last read: '1.,'
|
||||
byte 109: [json.exception.parse_error.101] parse error at line 6, column 22: syntax error while parsing object - unexpected end of input; expected '}'
|
||||
|
||||
valid JSON: false
|
||||
{
|
||||
"name": "Hello World",
|
||||
"nested": {
|
||||
"x": 1
|
||||
},
|
||||
"size": 1,
|
||||
"tags": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"valid": null
|
||||
}
|
||||
@@ -14,8 +14,8 @@ C++ types, and finally serialize it again.
|
||||
[parsing untrusted input](parsing/untrusted_input.md).
|
||||
- [Zero-copy JSON views](json_view.md) — read a JSON text through a flat index instead of building a `json` tree;
|
||||
strings and numbers stay in the input and are only decoded when needed.
|
||||
[Editable documents](json_view.md#editing-a-document) can also be modified, and [images](json_view.md#images) load a
|
||||
parsed document again without parsing it.
|
||||
- [Editable documents](json_view.md#editing-a-document) — modify a parsed document without building a `json` tree.
|
||||
- [Images](json_view.md#images) — save a parsed document and load it again without parsing it.
|
||||
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
|
||||
|
||||
## Accessing and modifying values
|
||||
|
||||
@@ -67,9 +67,10 @@ table). [`parse_copy()`](../api/basic_json_document/parse_copy.md) always owns a
|
||||
|
||||
!!! warning "A borrowed document depends on your buffer"
|
||||
|
||||
If a document borrows its text, that text **must outlive the document** (and every view taken from it). Reading
|
||||
or writing through a view after the underlying buffer is gone is undefined behavior, exactly as it would be for
|
||||
a dangling `#!cpp std::string_view`.
|
||||
If a document borrows its text, that text **must outlive the document** (and every view taken from it) and must
|
||||
stay **unmodified** for as long as the document exists, since floating-point values and unescaped strings are
|
||||
read from it lazily. Reading or writing through a view after the underlying buffer is gone is undefined behavior,
|
||||
exactly as it would be for a dangling `#!cpp std::string_view`.
|
||||
|
||||
A view is valid only while all of the following hold:
|
||||
|
||||
@@ -77,7 +78,7 @@ A view is valid only while all of the following hold:
|
||||
- the document has not been re-parsed since the view was taken (with [`read()`](../api/basic_json_document/read.md)
|
||||
or [`parse()`](../api/basic_json_document/parse.md) into it),
|
||||
- the document has not had [`shrink_to_fit()`](../api/basic_json_document/shrink_to_fit.md) called on it since, and
|
||||
- if the document borrows its source text, that text is still alive.
|
||||
- if the document borrows its source text, that text is still alive and unmodified.
|
||||
|
||||
Moving the `basic_json_document<>` itself is fine and does **not** invalidate its views: the index is a separate heap
|
||||
allocation that keeps its address across the move. Take a fresh view from [`root()`](../api/basic_json_document/root.md)
|
||||
@@ -167,10 +168,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
|
||||
If an object in the source text repeats a key, [`operator[]`](../api/basic_json_view/operator%5B%5D.md),
|
||||
[`at`](../api/basic_json_view/at.md), [`find`](../api/basic_json_view/find.md),
|
||||
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md),
|
||||
[`count`](../api/basic_json_view/count.md), and JSON pointer resolution all return the **first** member with that
|
||||
key. `basic_json::parse()` instead keeps the **last** value of a repeated key, so for `#!cpp {"a":1,"a":2}`,
|
||||
`#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is `#!cpp 2`.
|
||||
[`value`](../api/basic_json_view/value.md), [`contains`](../api/basic_json_view/contains.md), and JSON pointer
|
||||
resolution all return the **first** member with that key. `basic_json::parse()` instead keeps the **last** value
|
||||
of a repeated key, so for `#!cpp {"a":1,"a":2}`, `#!cpp view["a"]` is `#!cpp 1` while `#!cpp parse(text)["a"]` is
|
||||
`#!cpp 2`. [`count`](../api/basic_json_view/count.md) counts every member with the key, so it is `#!cpp 2` here.
|
||||
|
||||
[`begin()`](../api/basic_json_view/begin.md)/[`end()`](../api/basic_json_view/end.md) and
|
||||
[`items()`](../api/basic_json_view/items.md) visit *every* occurrence, in document order, and
|
||||
@@ -179,7 +180,7 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
|
||||
**Why the first?** A lookup can stop as soon as it finds a match. Returning the last member would force every lookup
|
||||
to scan all members of the object, even when the key is found at the very first one: this made lookups in small
|
||||
objects 1.6 to 3.4 times slower. The hash index of objects with 128 members or more leads to the first member of a
|
||||
objects 1.6 to 3.4 times slower. The hash index of the keys of objects with 128 members or more leads to the first member of a
|
||||
key as well. Other zero-copy parsers that index the source text, such as yyjson and simdjson, also return the first
|
||||
member. RFC 8259 only says that names within an object SHOULD be unique and that the behavior of a receiver that sees
|
||||
duplicates is unpredictable, so neither choice is wrong.
|
||||
|
||||
@@ -133,6 +133,14 @@ When defined, default parse and serialize functions for enums are excluded and h
|
||||
|
||||
See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md).
|
||||
|
||||
## `JSON_DISABLE_STREAM_BULK_SCAN`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp std::istream` reads one character at a time instead of scanning the
|
||||
characters that the stream buffer holds in bulk. Results are the same either way; the macro is an escape hatch for
|
||||
stream buffers that do not follow the usual `std::streambuf` contract. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md).
|
||||
|
||||
## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`
|
||||
|
||||
When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON
|
||||
@@ -141,6 +149,14 @@ is planned to become the default in version 4.0.0.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md).
|
||||
|
||||
## `JSON_DISABLE_UNLOCKED_FILE_IO`
|
||||
|
||||
When defined to `1`, parsing from a `#!cpp FILE*` reads every character with `std::fgetc`, which locks the file each
|
||||
time, instead of locking the file once and reading it with the unlocked stdio functions. Results are the same either
|
||||
way. The default value is `0`.
|
||||
|
||||
See [full documentation of `JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md).
|
||||
|
||||
## `JSON_NO_AUTOMATIC_UDLS`
|
||||
|
||||
When defined, `<nlohmann/json.hpp>` does not include `<nlohmann/json_literals.hpp>` with the user-defined string literals
|
||||
|
||||
@@ -23,6 +23,12 @@ The complete default namespace name is derived as follows:
|
||||
- [`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md) defined non-zero appends `_sbu8`.
|
||||
- [`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md) defined non-zero
|
||||
appends `_ekmo`.
|
||||
- [`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) defined non-zero appends
|
||||
`_dsbs`.
|
||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
||||
`_dufio`.
|
||||
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
||||
- [`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md) defined appends `_vssse3`.
|
||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||
below.
|
||||
@@ -105,5 +111,9 @@ follows:
|
||||
- Added ABI tags `_bics` ([`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md)), `_psp`
|
||||
([`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md)), `_snul`
|
||||
([`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md)), `_sbu8`
|
||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), and `_ekmo`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)) in version 3.13.0.
|
||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
||||
([`JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS`](../api/macros/json_use_objects_for_enum_keyed_maps.md)), `_dsbs`
|
||||
([`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md)), `_dufio`
|
||||
([`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_vns`
|
||||
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
||||
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
||||
@@ -0,0 +1,122 @@
|
||||
# Error Recovery
|
||||
|
||||
By default, parsing stops at the first error. With the [SAX interface](sax_interface.md), you can instead ask the
|
||||
parser to *recover*: to repair the error and continue, so that you get as much as possible out of malformed input, for
|
||||
instance a file that was cut off, JSON edited by hand, or the output of a language model.
|
||||
|
||||
## Recovering from errors
|
||||
|
||||
The SAX parser's [`parse_error`](../../api/json_sax/parse_error.md) function is called for every error. Its return value
|
||||
decides what happens next:
|
||||
|
||||
- `#!cpp false` stops parsing. This is what the SAX parsers of the library do, so [`parse`](../../api/basic_json/parse.md)
|
||||
and [`accept`](../../api/basic_json/accept.md) never recover.
|
||||
- `#!cpp true` repairs the error and continues parsing.
|
||||
|
||||
When recovering, the SAX parser still receives well-formed events: every `start_object` or `start_array` is followed by
|
||||
the matching `end_object` or `end_array`, and every `key` is followed by exactly one value. A SAX parser that creates a
|
||||
JSON value, such as the one in the example below, therefore gets a complete value. Parsing always ends, and
|
||||
[`sax_parse`](../../api/basic_json/sax_parse.md) returns `#!cpp false` for input that is not valid JSON, even if every
|
||||
error was repaired. Each token is reported at most once, and the SAX parser can stop at any error by returning
|
||||
`#!cpp false`.
|
||||
|
||||
!!! example
|
||||
|
||||
The example below derives a SAX parser from the library's parser for `json` values (`json_sax_dom_parser`),
|
||||
and recovers from all errors.
|
||||
|
||||
```cpp
|
||||
--8<-- "examples/sax_parse__error_recovery.cpp"
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
--8<-- "examples/sax_parse__error_recovery.output"
|
||||
```
|
||||
|
||||
## How errors are repaired
|
||||
|
||||
Each error is repaired with the smallest local edit: a missing separator is inserted, a stray token is removed, what can
|
||||
be read of a broken string or number is kept, and a value that cannot be read at all becomes `#!json null`.
|
||||
|
||||
| Mistake | Repair | Example | Result |
|
||||
|---------------------------|--------------------------------------------------------------------------------|------------------------------------------|----------------------------|
|
||||
| missing `,` or `:` | inserted | `#!json [1 2]`, `#!json {"a" 1}` | `[1,2]`, `{"a":1}` |
|
||||
| missing value | `#!json null` for an object key or between commas in an array | `#!json {"a":}`, `#!json [1,,2]` | `{"a":null}`, `[1,null,2]` |
|
||||
| trailing comma | removed | `#!json [1,2,]` | `[1,2]` |
|
||||
| broken string | invalid escapes and bytes are replaced (see below); a line break ends the string | `#!json ["a\qb"]` | `["aqb"]` |
|
||||
| broken number | the longest valid beginning is kept | `#!json [1., 2e+]` | `[1,2]` |
|
||||
| unreadable value | `#!json null` | `#!json [1, NaN, tru]` | `[1,null,null]` |
|
||||
| number too large | passed as infinity, together with its text | `#!json [1e999]` | infinity (see below) |
|
||||
| stray `:` | removed | `#!json ["a":1]` | `["a",1]` |
|
||||
| member without a key | skipped up to the next `,` or `}` | `#!json {1:2, "b":3}` | `{"b":3}` |
|
||||
| wrong closing bracket | closes the innermost array or object | `#!json {"a":[1,2}, "b":3}` | `{"a":[1,2],"b":3}` |
|
||||
| input ends too early | all open arrays and objects are closed | `#!json {"a":[1,2` | `{"a":[1,2]}` |
|
||||
| text before the value | skipped | `#!json )]}'{"a":1}` | `{"a":1}` |
|
||||
|
||||
In a string, an unknown escape like `\q` stands for the escaped character (`q`), as in JavaScript. An invalid `\u`
|
||||
escape, a lone surrogate, and ill-formed UTF-8 are each replaced by U+FFFD (REPLACEMENT CHARACTER), and control
|
||||
characters are kept. A string without its closing quote ends at the next line break or at the end of the input.
|
||||
|
||||
The input after the top-level value is not repaired: as without recovery, it is reported as an error, and parsing stops.
|
||||
|
||||
## Binary formats
|
||||
|
||||
The binary formats ([BJData](../binary_formats/bjdata.md), [BON8](../binary_formats/bon8.md),
|
||||
[BSON](../binary_formats/bson.md), [CBOR](../binary_formats/cbor.md), [MessagePack](../binary_formats/messagepack.md),
|
||||
and [UBJSON](../binary_formats/ubjson.md)) have no delimiters to find the next value by. So what can be repaired depends
|
||||
on whether the end of the item with the error is known, a distinction that
|
||||
[RFC 8949, Section 5.3](https://www.rfc-editor.org/rfc/rfc8949.html#section-5.3) makes for CBOR, too.
|
||||
|
||||
If the item is complete, but cannot be passed on as it is, it is replaced, and parsing continues after it:
|
||||
|
||||
| Mistake | Formats | Repair |
|
||||
|---------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------|
|
||||
| tag, if `tag_handler` is `cbor_tag_handler_t::error` (the default) | CBOR | ignored |
|
||||
| simple value other than `false`, `true`, and `null`, like undefined | CBOR | `#!json null` |
|
||||
| number too large for a custom `number_float_t`, like `float` | all | infinity |
|
||||
| character (`C`) that is not ASCII | BJData, UBJSON | U+FFFD |
|
||||
| invalid high-precision number (`H`) | BJData, UBJSON | the longest valid beginning is kept, as for JSON text, or `#!json null` |
|
||||
| high-precision number too large | BJData, UBJSON | passed as infinity, together with its text |
|
||||
| object key that is not a string | BON8, CBOR, MessagePack | the member is skipped |
|
||||
| element of a type the library does not read, like ObjectId or date | BSON | `#!json null` |
|
||||
| string without its terminator | BSON | kept |
|
||||
| document whose size does not match its content | BSON | kept |
|
||||
|
||||
CBOR tags and simple values are repaired as [RFC 8949, Section 6.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-6.1)
|
||||
suggests for converting CBOR to JSON. By default, [`sax_parse`](../../api/basic_json/sax_parse.md) reports every tag as
|
||||
an error; when recovering, tags are then ignored like with
|
||||
[`cbor_tag_handler_t::ignore`](../../api/basic_json/cbor_tag_handler_t.md). Strings that are not valid UTF-8 are no
|
||||
error: like [`from_cbor`](../../api/basic_json/from_cbor.md) and the other functions by default, `sax_parse` passes
|
||||
them on as they are.
|
||||
|
||||
After any other error, the end of the item is unknown: the input ended, a byte is not a valid type marker, or a size
|
||||
cannot be right. Parsing then stops, and the value read so far is completed: a key that waits for its value gets
|
||||
`#!json null`, and all open arrays and objects are closed. This keeps everything before the error of an input that was
|
||||
cut off. The exception is BSON, which stores the size of every document: an element whose end is unknown gets
|
||||
`#!json null`, the rest of its document is skipped, and parsing continues after the document.
|
||||
|
||||
## Limitations
|
||||
|
||||
- A repair is a guess. For example, `#!json {"a" "b": 1}` could be meant as `#!json {"a": "b"}` or as
|
||||
`#!json {"a": null, "b": 1}`; it is repaired to the former. Treat recovered values as a best effort, and check the
|
||||
reported errors.
|
||||
- A closing bracket always closes the innermost array or object. If a bracket is missing rather than wrong, the
|
||||
repair differs from the intention: `#!json {"a": {"b": [1, 2}, "c": 3}` is repaired to
|
||||
`#!json {"a": {"b": [1, 2], "c": 3}}`, although `#!json {"a": {"b": [1, 2]}, "c": 3}` may have been meant.
|
||||
- Keys without quotes, and strings in single quotes, are not supported; such members are skipped.
|
||||
- In the binary formats, a member that is skipped because its key is not a string is lost, and so are the elements of a
|
||||
BSON document after one whose end is unknown.
|
||||
- A number that is too large for `number_float_t` is passed as positive or negative infinity. The SAX parser's
|
||||
`number_float` also gets the number's text, but a JSON value cannot store it, and
|
||||
[`dump`](../../api/basic_json/dump.md) serializes infinity as `#!json null`.
|
||||
- When parsing is not strict (see [`sax_parse`](../../api/basic_json/sax_parse.md)), a repair may read parts of the
|
||||
input after the value, for instance of the next value in a stream of concatenated values.
|
||||
|
||||
## See also
|
||||
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [`parse_error`](../../api/json_sax/parse_error.md) - the SAX event for parse errors
|
||||
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
@@ -55,6 +55,30 @@ whitespace, because `operator>>` consumes the character that terminates a number
|
||||
[`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the
|
||||
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||
|
||||
## Input sources and performance
|
||||
|
||||
All inputs produce the same values and the same exceptions, but they are read in different ways, which makes some of
|
||||
them faster than others:
|
||||
|
||||
| input | how it is read |
|
||||
|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||
| strings, string views, string literals, contiguous containers, pointer or iterator ranges over contiguous memory | the whole input is scanned in bulk: runs of string characters, numbers, and indentation are recognized several bytes at a time |
|
||||
| `#!cpp std::istream` (`#!cpp std::ifstream`, `#!cpp std::istringstream`, ...) | the characters the stream buffer holds are scanned in bulk like a string; the stream buffer is asked for more when they are used up |
|
||||
| `#!cpp FILE*` | one character at a time, with the file locked once for the whole parse |
|
||||
| other iterator ranges (e.g., `#!cpp std::list<char>`, `#!cpp std::istreambuf_iterator<char>`), wide strings | one character at a time |
|
||||
|
||||
Parsing a file from a `#!cpp std::ifstream` is therefore only about 10 to 20 % slower than reading the whole file into a
|
||||
`#!cpp std::string` first and parsing that, and it needs no memory for a copy of the file. The stream is never read
|
||||
ahead: after parsing, it is positioned exactly as if every character had been read one at a time, so
|
||||
[`operator>>`](../../api/operator_gtgt.md) can still read several values from one stream (see also
|
||||
[JSON Lines](json_lines.md)).
|
||||
|
||||
Two macros switch these optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not
|
||||
follow the usual `std::streambuf` contract, and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../../api/macros/json_disable_unlocked_file_io.md) for programs in which another
|
||||
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
The library offers two parsing models:
|
||||
@@ -75,7 +99,7 @@ You can influence a DOM parse without switching to the SAX interface by passing
|
||||
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
|
||||
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
|
||||
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
|
||||
options.
|
||||
options. To get as much as possible out of malformed input, a SAX parser can [recover from errors](error_recovery.md).
|
||||
|
||||
## See also
|
||||
|
||||
@@ -86,4 +110,5 @@ options.
|
||||
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
|
||||
- [SAX interface](sax_interface.md) - implement a custom SAX handler
|
||||
- [parsing and exceptions](parse_exceptions.md) - control error handling
|
||||
- [error recovery](error_recovery.md) - get as much as possible out of malformed input
|
||||
- [parsing untrusted input](untrusted_input.md) - what to consider when parsing input from untrusted sources
|
||||
@@ -64,7 +64,8 @@ bool parse_error(std::size_t position,
|
||||
const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value indicates whether the parsing should continue, so the function should usually return `#!cpp false`.
|
||||
The return value decides whether to stop parsing (`#!cpp false`) or to repair the error and continue
|
||||
(`#!cpp true`); see [error recovery](error_recovery.md) for the latter.
|
||||
|
||||
??? example "Example: report parse errors without exceptions"
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ bool key(string_t& val);
|
||||
bool parse_error(std::size_t position, const std::string& last_token, const json::exception& ex);
|
||||
```
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
The return value of each function determines whether parsing should proceed. For `parse_error`, returning
|
||||
`#!cpp true` [recovers from the error](error_recovery.md).
|
||||
|
||||
To implement your own SAX handler, proceed as follows:
|
||||
|
||||
@@ -68,7 +69,7 @@ To implement your own SAX handler, proceed as follows:
|
||||
2. Create an object of your SAX interface class, e.g. `my_sax`.
|
||||
3. Call `#!cpp bool json::sax_parse(input, &my_sax);` where the first parameter can be any input like a string or an input stream and the second parameter is a pointer to your SAX interface.
|
||||
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating the result of the last executed SAX event. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
Note the `sax_parse` function only returns a `#!cpp bool` indicating whether the input was parsed without errors and no SAX event returned `#!cpp false`. It does not return `json` value - it is up to you to decide what to do with the SAX events. Furthermore, no exceptions are thrown in case of a parse error - it is up to you what to do with the exception object passed to your `parse_error` implementation. Internally, the SAX interface is used for the DOM parser (class `json_sax_dom_parser`) as well as the acceptor (`json_sax_acceptor`), see file `json_sax.hpp`.
|
||||
|
||||
## See also
|
||||
|
||||
|
||||
@@ -20,32 +20,39 @@ below apply on every supported standard. This adapter exposes two optimizations
|
||||
|
||||
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
||||
read, and
|
||||
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one
|
||||
character (and one function call) at a time.
|
||||
- the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
|
||||
a time, rather than one character (and one function call) at a time.
|
||||
|
||||
A `#!cpp std::istream` (including `#!cpp std::ifstream`) or `#!cpp FILE*`, by contrast, is read through
|
||||
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time
|
||||
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any
|
||||
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g.
|
||||
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path
|
||||
additionally requires contiguous storage.
|
||||
A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
|
||||
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
||||
locks the file once per parse instead of once per character. See
|
||||
[input sources and performance](parsing/index.md#input-sources-and-performance) for the full table. According to the
|
||||
description of [#5828](https://github.com/nlohmann/json/pull/5828), parsing from a stream is within about 10 to 20 % of
|
||||
parsing an equivalent `#!cpp std::string`.
|
||||
|
||||
Practically: if the JSON text is already in memory, or small enough to read into memory, prefer passing a
|
||||
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a
|
||||
file, that means reading it into a string first and then parsing the string, rather than passing a
|
||||
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization:
|
||||
Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
|
||||
`#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
|
||||
iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
|
||||
optimization but not the second, since the byte-scanning fast path requires contiguous storage.
|
||||
|
||||
Practically: a file can be passed directly to `parse`, which also avoids the memory for a copy of the file. Reading it
|
||||
into a string first is no longer necessary for speed:
|
||||
|
||||
```cpp
|
||||
// gets the contiguous fast paths
|
||||
// fast: the stream buffer's get area is scanned in bulk
|
||||
std::ifstream f("example.json");
|
||||
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
json j = json::parse(contents);
|
||||
|
||||
// does not: input_stream_adapter has no fast path
|
||||
std::ifstream f2("example.json");
|
||||
json j2 = json::parse(f2);
|
||||
json j = json::parse(f);
|
||||
```
|
||||
|
||||
Two macros switch the stream optimizations off without changing any result:
|
||||
[`JSON_DISABLE_STREAM_BULK_SCAN`](../api/macros/json_disable_stream_bulk_scan.md) for stream buffers that do not follow
|
||||
the usual `#!cpp std::streambuf` contract, and
|
||||
[`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) for programs in which another thread
|
||||
must use the same `#!cpp FILE` object while it is parsed.
|
||||
|
||||
For contiguous input with many non-ASCII characters, [`JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md) can
|
||||
additionally speed up UTF-8 validation by using the [simdutf](https://github.com/simdutf/simdutf) library instead of
|
||||
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
|
||||
|
||||
@@ -194,9 +194,9 @@ packet-beta
|
||||
|
||||
| Bytes | Field | Type | Contents |
|
||||
|-------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below) |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bits 3 and 4: moved and new (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their hash index (1-based), or 0; otherwise 0 |
|
||||
| 0 | `kind` | `uint8_t` | the type, numbered as [`value_t`](../api/basic_json/value_t.md): 0 null, 1 object, 2 array, 3 string, 4 boolean, 5 signed integer, 6 unsigned integer, 7 float; 10 for a link (see below). There is no node for a discarded value: a [discarded](../api/basic_json_view/is_discarded.md) view has none |
|
||||
| 1 | `flags` | `uint8_t` | bits 0-1: where a string's bytes (or a number's token) are (0: the source text, 1: the buffer of decoded strings, for strings with escapes, 2: the edit buffer); bit 2: the value of a boolean; bit 3: moved, bit 4: new, bit 5: linked (see below) |
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||
@@ -212,7 +212,7 @@ packet-beta
|
||||
step from element to element this way and skip whole subtrees in constant time.
|
||||
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
|
||||
2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
|
||||
- **Large objects** (128 members or more) get a hash index after parsing
|
||||
- **Large objects** (128 members or more) get a hash index for their keys after parsing
|
||||
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||
not compare every key. Of duplicate keys the table leads to the first, as a linear search does. The object's `extra`
|
||||
@@ -257,6 +257,8 @@ edited:
|
||||
sequence (a header node, then the entries), whose number is in `off`. The entries are links (`kind` 10), whose bytes
|
||||
8-15 hold the address of the value's node, so values never move.
|
||||
- A node written by an edit gets the flag *new* (bit 4): it has no position in the source text.
|
||||
- A value that an entry of such a sequence links to gets the flag *linked* (bit 5): its extent in the parsed layout
|
||||
(`next`) no longer matters, so a scalar can replace an array or object there in place.
|
||||
- Views of read-only documents compile without any of this: how views walk the index is a template parameter
|
||||
(`navigation<Editable>`).
|
||||
|
||||
|
||||
@@ -972,9 +972,19 @@ double-precision number when `number_float_t` is `#!cpp float`.
|
||||
|
||||
### json.exception.out_of_range.407
|
||||
|
||||
An unsigned integer number greater than 9223372036854775807 cannot be represented in the binary format. This exception
|
||||
is only thrown by [`to_bon8`](../api/basic_json/to_bon8.md), as [BON8](../features/binary_formats/bon8.md) has no
|
||||
unsigned 64-bit integer type.
|
||||
An integer number cannot be represented by the binary format it is serialized to:
|
||||
|
||||
- [BON8](../features/binary_formats/bon8.md) only stores integers that fit into int64, so
|
||||
[`to_bon8`](../api/basic_json/to_bon8.md) throws for unsigned integers greater than 9223372036854775807.
|
||||
- [CBOR](../features/binary_formats/cbor.md), [MessagePack](../features/binary_formats/messagepack.md), and
|
||||
[BSON](../features/binary_formats/bson.md) store integers in at most 64 bits. With the default number types, every
|
||||
integer fits, but a [`number_integer_t`](../api/basic_json/number_integer_t.md) or
|
||||
[`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) wider than 64 bits (e.g., `__int128`) can hold values
|
||||
outside the range of the format: [-2^64, 2^64-1] for CBOR, [-2^63, 2^64-1] for MessagePack, and the range of int64
|
||||
(signed integers) or uint64 (unsigned integers) for BSON.
|
||||
|
||||
[UBJSON](../features/binary_formats/ubjson.md) and [BJData](../features/binary_formats/bjdata.md) never throw this
|
||||
exception, because they serialize integers beyond 64 bits as high-precision numbers.
|
||||
|
||||
!!! note "Former occurrences"
|
||||
|
||||
@@ -983,11 +993,17 @@ unsigned 64-bit integer type.
|
||||
- Since version 3.9.0, integer numbers beyond int64 are serialized as high-precision UBJSON numbers.
|
||||
- Since version 3.12.0, integer numbers beyond int64 are serialized as uint64 BSON numbers.
|
||||
|
||||
!!! failure "Example message"
|
||||
Before version 3.13.0, CBOR, MessagePack, and BSON silently truncated integers wider than 64 bits, and BJData
|
||||
truncated unsigned integers wider than 64 bits.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
```
|
||||
integer number 18446744073709551615 cannot be represented by BON8 as it does not fit int64
|
||||
```
|
||||
```
|
||||
integer number 1267650600228229401496703205376 cannot be represented by CBOR as it does not fit [-2^64, 2^64-1]
|
||||
```
|
||||
|
||||
### json.exception.out_of_range.408
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ nav:
|
||||
- features/performance.md
|
||||
- Parsing:
|
||||
- features/parsing/index.md
|
||||
- features/parsing/error_recovery.md
|
||||
- features/parsing/json_lines.md
|
||||
- features/parsing/parse_exceptions.md
|
||||
- features/parsing/parser_callbacks.md
|
||||
@@ -370,7 +371,9 @@ nav:
|
||||
- 'JSON_DELETE_DEPRECATED_FUNCTIONS': api/macros/json_delete_deprecated_functions.md
|
||||
- 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md
|
||||
- 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md
|
||||
- 'JSON_DISABLE_STREAM_BULK_SCAN': api/macros/json_disable_stream_bulk_scan.md
|
||||
- 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md
|
||||
- 'JSON_DISABLE_UNLOCKED_FILE_IO': api/macros/json_disable_unlocked_file_io.md
|
||||
- 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20, JSON_HAS_CPP_23, JSON_HAS_CPP_26': api/macros/json_has_cpp_11.md
|
||||
- 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md
|
||||
- 'JSON_HAS_RANGE_VIEW_CONVERSION': api/macros/json_has_range_view_conversion.md
|
||||
|
||||
@@ -54,6 +54,14 @@
|
||||
#define JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define JSON_DISABLE_STREAM_BULK_SCAN 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define JSON_DISABLE_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -102,14 +110,38 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN _dsbs
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO _dufio
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_USE_SSSE3
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3 _vssse3
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h) json_abi ## a ## b ## c ## d ## e ## f ## g ## h
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f, g, h, i, j, k, l) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f, g, h, i, j, k, l)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
@@ -120,7 +152,11 @@
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_BINARY_UTF8, \
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS)
|
||||
NLOHMANN_JSON_ABI_TAG_OBJECTS_FOR_ENUM_KEYED_MAPS, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_STREAM_BULK_SCAN, \
|
||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -1342,14 +1342,32 @@ inline char* write_shortest(char* first, const zmij::shortest_decimal d) noexcep
|
||||
return end + (three ? 5 : 4);
|
||||
}
|
||||
|
||||
/// the powers of ten up to 10^16
|
||||
inline const std::array<std::uint64_t, 17>& powers_of_ten_16() noexcept
|
||||
/// the powers of ten up to 10^19 (all that fit in std::uint64_t)
|
||||
inline const std::array<std::uint64_t, 20>& powers_of_ten() noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 17> powers =
|
||||
static const std::array<std::uint64_t, 20> powers =
|
||||
{
|
||||
{
|
||||
1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u,
|
||||
100000000000u, 1000000000000u, 10000000000000u, 100000000000000u, 1000000000000000u, 10000000000000000u
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
}
|
||||
};
|
||||
return powers;
|
||||
@@ -1367,9 +1385,9 @@ JSON_HEDLEY_NON_NULL(1)
|
||||
JSON_HEDLEY_RETURNS_NON_NULL
|
||||
inline char* write_short_decimal(char* first, std::uint64_t digits, int count, int exp) noexcept
|
||||
{
|
||||
JSON_ASSERT(digits >= powers_of_ten_16()[static_cast<std::size_t>(count - 1)] && count <= 15);
|
||||
JSON_ASSERT(digits >= powers_of_ten()[static_cast<std::size_t>(count - 1)] && count <= 15);
|
||||
const int scale = 16 - count;
|
||||
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten_16()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
|
||||
return write_shortest(first, zmij::shortest_decimal{digits * powers_of_ten()[static_cast<std::size_t>(scale)], exp - scale - 1, 0, false});
|
||||
}
|
||||
|
||||
/// as write_short_decimal(), counting the digits (not 0, less than 10^15)
|
||||
@@ -1380,7 +1398,7 @@ inline char* write_short_decimal(char* first, std::uint64_t digits, int exp) noe
|
||||
JSON_ASSERT(digits != 0 && digits < 1000000000000000u);
|
||||
// floor(log10(2^bits)) + 1 digits, or one less
|
||||
const int log2_bound = ((64 - count_leading_zeros(digits)) * 1233) >> 12;
|
||||
const int count = log2_bound + (digits >= powers_of_ten_16()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
|
||||
const int count = log2_bound + (digits >= powers_of_ten()[static_cast<std::size_t>(log2_bound)] ? 1 : 0);
|
||||
return write_short_decimal(first, digits, count, exp);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -14,10 +14,12 @@
|
||||
#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 <streambuf> // streambuf
|
||||
#include <string> // string, char_traits
|
||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||
#include <utility> // pair, declval
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
#include <cstdio> // FILE *
|
||||
@@ -30,6 +32,33 @@
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
// Which unlocked stdio functions file_input_adapter uses (see
|
||||
// JSON_DISABLE_UNLOCKED_FILE_IO): 1 = POSIX flockfile/getc_unlocked,
|
||||
// 2 = Microsoft CRT _lock_file/_fgetc_nolock, 0 = none (std::fgetc, which
|
||||
// locks the file for every character). Only C libraries known to declare
|
||||
// the functions are listed; the macros are tested after <cstdio>, which
|
||||
// defines __GLIBC__. MemorySanitizer builds always use std::fgetc: glibc's
|
||||
// inline getc_unlocked reads the stdio buffer directly, which glibc (not
|
||||
// instrumented) filled, so MemorySanitizer would report a false positive.
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(memory_sanitizer)
|
||||
#define JSON_UNLOCKED_FILE_IO_MSAN 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO || defined(JSON_UNLOCKED_FILE_IO_MSAN)
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#elif defined(_MSC_VER)
|
||||
#define JSON_UNLOCKED_FILE_IO 2
|
||||
#elif defined(__GLIBC__) || defined(__APPLE__) || defined(__ANDROID__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
|
||||
#define JSON_UNLOCKED_FILE_IO 1
|
||||
#else
|
||||
#define JSON_UNLOCKED_FILE_IO 0
|
||||
#endif
|
||||
#undef JSON_UNLOCKED_FILE_IO_MSAN
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
@@ -56,18 +85,35 @@ class file_input_adapter
|
||||
: m_file(f)
|
||||
{
|
||||
JSON_ASSERT(m_file != nullptr);
|
||||
lock();
|
||||
}
|
||||
|
||||
// make class move-only
|
||||
file_input_adapter(const file_input_adapter&) = delete;
|
||||
file_input_adapter(file_input_adapter&&) noexcept = default;
|
||||
file_input_adapter(file_input_adapter&& rhs) noexcept
|
||||
: m_file(rhs.m_file)
|
||||
{
|
||||
// the lock is owned by the thread, not the adapter: hand it over
|
||||
rhs.m_file = nullptr;
|
||||
}
|
||||
file_input_adapter& operator=(const file_input_adapter&) = delete;
|
||||
file_input_adapter& operator=(file_input_adapter&&) = delete;
|
||||
~file_input_adapter() = default;
|
||||
~file_input_adapter()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
std::char_traits<char>::int_type get_character() noexcept
|
||||
{
|
||||
// the file is locked for the lifetime of the adapter, so the
|
||||
// per-character lock of std::fgetc can be skipped
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
return getc_unlocked(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
return _fgetc_nolock(m_file);
|
||||
#else
|
||||
return std::fgetc(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns the number of characters successfully read
|
||||
@@ -78,6 +124,32 @@ class file_input_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
// Lock the file once for the whole parse instead of once per character.
|
||||
// The lock is recursive, so fread() and the user's own stdio calls on the
|
||||
// same thread keep working.
|
||||
void lock() noexcept
|
||||
{
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
flockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_lock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
void unlock() noexcept
|
||||
{
|
||||
// a moved-from adapter has no file and holds no lock
|
||||
if (m_file == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#if JSON_UNLOCKED_FILE_IO == 1
|
||||
funlockfile(m_file);
|
||||
#elif JSON_UNLOCKED_FILE_IO == 2
|
||||
_unlock_file(m_file);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// the file pointer to read from
|
||||
std::FILE* m_file;
|
||||
};
|
||||
@@ -127,15 +199,26 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
, lookahead(rhs.lookahead)
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
, token_carry(std::move(rhs.token_carry))
|
||||
, token_segment(rhs.token_segment)
|
||||
, current_in_get_area(rhs.current_in_get_area)
|
||||
, tracking_token(rhs.tracking_token)
|
||||
#endif
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
rhs.lookahead = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
@@ -155,17 +238,41 @@ class input_stream_adapter
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// sgetc() 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->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
current_in_get_area = false;
|
||||
#endif
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the peeked character is at gptr(), unless the streambuf has
|
||||
// no get area
|
||||
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_end(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@@ -179,13 +286,6 @@ class input_stream_adapter
|
||||
lookahead = false;
|
||||
}
|
||||
#else
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
}
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
@@ -194,12 +294,37 @@ class input_stream_adapter
|
||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
#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
|
||||
@@ -218,7 +343,165 @@ class input_stream_adapter
|
||||
return res;
|
||||
}
|
||||
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// The lexer may scan the characters in the get area of the streambuf
|
||||
// directly (SWAR fast paths). The get area is only a window into the
|
||||
// input: it ends wherever the streambuf's buffer ends, and is refilled by
|
||||
// the next get_character(). The lexer therefore keeps counting the
|
||||
// position itself and treats the end of the window as "unknown", not as
|
||||
// the end of the input.
|
||||
static constexpr bool supports_bulk_scan = true;
|
||||
static constexpr bool bulk_window = true;
|
||||
|
||||
// Pointer to the next unread character; only valid when
|
||||
// bulk_remaining() > 0. The character before it is the one last returned
|
||||
// by get_character().
|
||||
const char* bulk_data() const
|
||||
{
|
||||
return current_end();
|
||||
}
|
||||
|
||||
// Number of unread characters in the get area.
|
||||
std::size_t bulk_remaining() const
|
||||
{
|
||||
if (!current_in_get_area)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::size_t>(streambuf_access::get_end(sb) - current_end());
|
||||
}
|
||||
|
||||
// Consume @a n characters previously inspected via bulk_data(); the last
|
||||
// of them becomes the character last returned by get_character().
|
||||
void bulk_skip(std::size_t n)
|
||||
{
|
||||
JSON_ASSERT(n <= bulk_remaining());
|
||||
// gbump() takes an int, but a get area (e.g., of a std::stringbuf) can
|
||||
// be larger
|
||||
constexpr auto max_bump = static_cast<std::size_t>((std::numeric_limits<int>::max)());
|
||||
for (; n > max_bump; n -= max_bump)
|
||||
{
|
||||
streambuf_access::bump(sb, static_cast<int>(max_bump));
|
||||
}
|
||||
streambuf_access::bump(sb, static_cast<int>(n));
|
||||
}
|
||||
|
||||
// The adapter keeps the current token for the lexer's error messages: the
|
||||
// part in the get area is only remembered as a pointer, and copied out
|
||||
// when the get area is about to be refilled.
|
||||
static constexpr bool supports_token_tracking = true;
|
||||
|
||||
// Start a token with the characters read from now on. Until this is
|
||||
// called, nothing is tracked (the binary readers never call it).
|
||||
void begin_token() noexcept
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
token_segment = current_end();
|
||||
}
|
||||
|
||||
// Start a new token with the character @a c last returned by
|
||||
// get_character().
|
||||
void mark_token_start(std::char_traits<char>::int_type c)
|
||||
{
|
||||
tracking_token = true;
|
||||
token_carry.clear();
|
||||
if (current_in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_segment = nullptr;
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the characters read since mark_token_start() to @a out, without the
|
||||
// last one if @a drop_last.
|
||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||
{
|
||||
out = token_carry;
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
out.insert(out.end(), token_segment, current_end());
|
||||
}
|
||||
if (drop_last && !out.empty())
|
||||
{
|
||||
out.pop_back();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#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
|
||||
// accessible; calling them on another streambuf object is then ordinary
|
||||
// member access through a pointer to member.
|
||||
struct streambuf_access : std::streambuf
|
||||
{
|
||||
static const char* get_ptr(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::gptr))();
|
||||
}
|
||||
static const char* get_end(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::egptr))();
|
||||
}
|
||||
static const char* get_begin(std::streambuf* s)
|
||||
{
|
||||
return (s->*(&streambuf_access::eback))();
|
||||
}
|
||||
static void bump(std::streambuf* s, int n)
|
||||
{
|
||||
(s->*(&streambuf_access::gbump))(n);
|
||||
}
|
||||
};
|
||||
|
||||
// one past the character last returned by get_character(), if that is in
|
||||
// the get area
|
||||
const char* current_end() const
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// the character is peeked at gptr() and not stepped over yet, unless
|
||||
// it was given back with release_lookahead()
|
||||
return streambuf_access::get_ptr(sb) + (lookahead ? 1 : 0);
|
||||
#else
|
||||
return streambuf_access::get_ptr(sb);
|
||||
#endif
|
||||
}
|
||||
|
||||
// the get area is about to be replaced: copy the token's part in it
|
||||
void save_token_segment()
|
||||
{
|
||||
if (token_segment != nullptr)
|
||||
{
|
||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
||||
token_segment = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (!tracking_token)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (in_get_area)
|
||||
{
|
||||
token_segment = current_end() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
token_carry.push_back(std::char_traits<char>::to_char_type(c));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
@@ -240,6 +523,16 @@ class input_stream_adapter
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
#endif
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
/// the part of the current token that was in earlier get areas
|
||||
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;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -314,17 +607,22 @@ class iterator_input_adapter
|
||||
return get_consumed_count() + eof_reads;
|
||||
}
|
||||
|
||||
// number of newlines in the consumed range [0, last_index); @a line_start
|
||||
// is set to the index after the last of them (0 if there is none); only
|
||||
// valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t last_index, std::size_t& line_start) const
|
||||
// number of newlines in the consumed range [first_index, last_index); if
|
||||
// there is one, @a line_start is set to the index after the last of them,
|
||||
// otherwise it is left unchanged; only valid when supports_seek
|
||||
std::size_t count_consumed_lines(std::size_t first_index, std::size_t last_index, std::size_t& line_start) const
|
||||
{
|
||||
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
|
||||
using difference_type = typename std::iterator_traits<IteratorType>::difference_type;
|
||||
const auto from = std::next(begin, static_cast<difference_type>(first_index));
|
||||
const auto to = std::next(from, static_cast<difference_type>(last_index - first_index));
|
||||
const auto newline = char_traits<char_type>::to_char_type('\n');
|
||||
using reverse_it = std::reverse_iterator<IteratorType>;
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(begin), newline);
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
return static_cast<std::size_t>(std::count(begin, last_newline.base(), newline));
|
||||
const auto last_newline = std::find(reverse_it(to), reverse_it(from), newline);
|
||||
if (last_newline.base() != from)
|
||||
{
|
||||
line_start = static_cast<std::size_t>(std::distance(begin, last_newline.base()));
|
||||
}
|
||||
return static_cast<std::size_t>(std::count(from, last_newline.base(), newline));
|
||||
}
|
||||
|
||||
// append the already-consumed characters in the half-open range
|
||||
|
||||
@@ -132,7 +132,9 @@ struct json_sax
|
||||
@param[in] position the position in the input where the error occurs
|
||||
@param[in] last_token the last read token
|
||||
@param[in] ex an exception object describing the error
|
||||
@return whether parsing should proceed (must return false)
|
||||
@return whether to recover from the error: false stops parsing; true
|
||||
repairs the error and continues, or, if that is not possible,
|
||||
stops after completing the value read so far
|
||||
*/
|
||||
virtual bool parse_error(std::size_t position,
|
||||
const std::string& last_token,
|
||||
@@ -326,9 +328,12 @@ a pointer to the respective array or object for each recursion depth.
|
||||
After successful parsing, the value that is passed by reference to the
|
||||
constructor contains the parsed value.
|
||||
|
||||
@tparam BasicJsonType the JSON type
|
||||
@tparam BasicJsonType the JSON type
|
||||
@tparam InputAdapterType the input adapter of the lexer that can be passed to
|
||||
the constructor to record diagnostic positions; it
|
||||
does not matter if no lexer is passed
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
class json_sax_dom_parser
|
||||
{
|
||||
public:
|
||||
@@ -552,7 +557,7 @@ class json_sax_dom_parser
|
||||
lexer_t* m_lexer_ref = nullptr;
|
||||
};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
template<typename BasicJsonType, typename InputAdapterType = string_input_adapter_type>
|
||||
class json_sax_dom_callback_parser
|
||||
{
|
||||
public:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -275,13 +275,77 @@ JSON_HEDLEY_ALWAYS_INLINE std::uint32_t parse_eight_digits(std::uint64_t v) noex
|
||||
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||
}
|
||||
|
||||
/// whether [first, last) contains a digit other than '0'
|
||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||
/// digits (after fast_float)
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool is_eight_digits(std::uint64_t v) noexcept
|
||||
{
|
||||
return std::any_of(first, last, [](char c) noexcept
|
||||
return ((v & 0xF0F0F0F0F0F0F0F0u) | (((v + 0x0606060606060606u) & 0xF0F0F0F0F0F0F0F0u) >> 4u)) == 0x3333333333333333u;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip the ASCII digits starting at data[i] and accumulate their value
|
||||
|
||||
Eight digits are taken at a time while at least eight bytes remain. @a w wraps
|
||||
around after 19 digits; the caller only uses it for shorter digit runs.
|
||||
|
||||
@return the index of the first byte at or after @a i that is not a digit
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t accumulate_digits(const char* data, std::size_t i, std::size_t avail, std::uint64_t& w) noexcept
|
||||
{
|
||||
while (avail - i >= 8)
|
||||
{
|
||||
return c != '0';
|
||||
});
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
if (!is_eight_digits(v))
|
||||
{
|
||||
break;
|
||||
}
|
||||
w = (w * 100000000u) + parse_eight_digits(v);
|
||||
i += 8;
|
||||
}
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(data[i] - '0');
|
||||
++i;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
/// whether [first, last) contains a digit other than '0'; a plain loop, as
|
||||
/// GCC calls std::any_of out of line even for the usual empty range
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool has_nonzero_digit(const char* first, const char* last) noexcept
|
||||
{
|
||||
for (; first != last; ++first)
|
||||
{
|
||||
if (*first != '0')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief accumulate the validated digits [p, last) into @a w, as long as
|
||||
@a remaining digits still fit into it
|
||||
|
||||
Eight digits are taken at a time while at least eight remain, and @a remaining
|
||||
is decreased by the number of digits taken.
|
||||
|
||||
@return a pointer past the last digit taken
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE const char* accumulate_significant_digits(const char* p, const char* last, int& remaining, std::uint64_t& w) noexcept
|
||||
{
|
||||
while (remaining >= 8 && last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||
@@ -327,7 +391,7 @@ truncated is set.
|
||||
std::string::npos if there is none
|
||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
||||
*/
|
||||
inline float_significand parse_float_significand(const char* first, const char* last,
|
||||
JSON_HEDLEY_ALWAYS_INLINE float_significand parse_float_significand(const char* first, const char* last,
|
||||
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||
{
|
||||
float_significand s;
|
||||
@@ -342,16 +406,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
||||
int remaining = 19; // digits that still fit into w
|
||||
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
||||
{
|
||||
while (remaining >= 8 && integer_last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != integer_last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
p = accumulate_significant_digits(p, integer_last, remaining, w);
|
||||
s.exponent = integer_last - p;
|
||||
s.truncated = has_nonzero_digit(p, integer_last);
|
||||
}
|
||||
@@ -370,16 +425,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
||||
s.exponent -= p - zeros;
|
||||
}
|
||||
const char* const digits = p;
|
||||
while (remaining >= 8 && mantissa_last - p >= 8)
|
||||
{
|
||||
w = (w * 100000000u) + parse_eight_digits(read_eight_bytes(p));
|
||||
p += 8;
|
||||
remaining -= 8;
|
||||
}
|
||||
for (; remaining > 0 && p != mantissa_last; ++p, --remaining)
|
||||
{
|
||||
w = (w * 10u) + static_cast<std::uint64_t>(*p - '0');
|
||||
}
|
||||
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
|
||||
s.exponent -= p - digits;
|
||||
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
|
||||
using parser_callback_t =
|
||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
||||
|
||||
/// whether a SAX handler ignores the text of float tokens (the library's own
|
||||
/// handlers do), so that the parser need not materialize it
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType>
|
||||
struct sax_ignores_number_text<json_sax_acceptor<BasicJsonType>> : std::true_type {};
|
||||
|
||||
/*!
|
||||
@brief syntax analysis
|
||||
|
||||
@@ -139,24 +153,59 @@ class parser
|
||||
bool accept(const bool strict = true)
|
||||
{
|
||||
json_sax_acceptor<BasicJsonType> sax_acceptor;
|
||||
return sax_parse(&sax_acceptor, strict);
|
||||
return sax_parse_impl<false>(&sax_acceptor, strict);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief public SAX interface
|
||||
|
||||
If the SAX parser's parse_error() returns true, the parser recovers from
|
||||
the error: it repairs the input and continues (see #3989).
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] strict whether to expect the last token to be EOF
|
||||
@return whether the input was parsed without errors and no SAX event
|
||||
returned false
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse(SAX* sax, const bool strict = true)
|
||||
{
|
||||
return sax_parse_impl<true>(sax, strict);
|
||||
}
|
||||
|
||||
private:
|
||||
/// what sax_parse_internal() does after an object key was expected
|
||||
enum class next_step : std::uint8_t
|
||||
{
|
||||
/// stop parsing
|
||||
stop,
|
||||
/// parse a value that begins with last_token
|
||||
parse_value,
|
||||
/// evaluate the state of the innermost container, which reads
|
||||
/// last_token again
|
||||
evaluate_state
|
||||
};
|
||||
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_impl(SAX* sax, const bool strict)
|
||||
{
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
const bool result = sax_parse_internal<AllowRecovery>(sax);
|
||||
|
||||
if (result)
|
||||
{
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
// strict mode: next byte must be EOF; after recovering from an
|
||||
// error, the end of the input may already have been read
|
||||
if (last_token != token_type::end_of_input && get_token() != token_type::end_of_input)
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
||||
// the value is complete, so there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr),
|
||||
std::integral_constant<bool, AllowRecovery> {}));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -167,10 +216,26 @@ class parser
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result && !error_reported;
|
||||
}
|
||||
|
||||
/// the text of the current float token for @a sax (see sax_ignores_number_text)
|
||||
template<typename SAX>
|
||||
const string_t& number_float_text(SAX* /*sax*/)
|
||||
{
|
||||
return number_float_text(std::integral_constant<bool, sax_ignores_number_text<SAX>::value> {});
|
||||
}
|
||||
|
||||
const string_t& number_float_text(std::true_type /*ignored*/) const noexcept
|
||||
{
|
||||
return m_lexer.get_ignored_number_text();
|
||||
}
|
||||
|
||||
string_t& number_float_text(std::false_type /*ignored*/)
|
||||
{
|
||||
return m_lexer.get_string();
|
||||
}
|
||||
|
||||
private:
|
||||
/*!
|
||||
@brief run a DOM SAX parser to completion and position the lexer
|
||||
|
||||
@@ -188,7 +253,7 @@ class parser
|
||||
template<typename DomSax>
|
||||
bool parse_dom(DomSax& sdp, const bool strict)
|
||||
{
|
||||
sax_parse_internal(&sdp);
|
||||
sax_parse_internal<false>(&sdp);
|
||||
|
||||
if (strict)
|
||||
{
|
||||
@@ -208,10 +273,20 @@ class parser
|
||||
return !sdp.is_errored();
|
||||
}
|
||||
|
||||
template<typename SAX>
|
||||
/*!
|
||||
@brief parse a JSON value and pass it to a SAX parser
|
||||
|
||||
@tparam AllowRecovery whether to recover from an error if the SAX parser's
|
||||
parse_error() returns true; false for the SAX parsers
|
||||
of parse() and accept(), which never do, so that no
|
||||
code for recovering is generated for them
|
||||
*/
|
||||
template<bool AllowRecovery, typename SAX>
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
bool sax_parse_internal(SAX* sax)
|
||||
{
|
||||
const std::integral_constant<bool, AllowRecovery> allow_recovery{};
|
||||
|
||||
// stack to remember the hierarchy of structured values we are parsing
|
||||
// true = array; false = object
|
||||
std::vector<bool> states;
|
||||
@@ -242,12 +317,20 @@ class parser
|
||||
break;
|
||||
}
|
||||
|
||||
// parse key
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
|
||||
// parse key (the steps of parse_key(), which are
|
||||
// repeated here and below for speed)
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -255,12 +338,13 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// remember we are now inside an object
|
||||
states.push_back(false);
|
||||
|
||||
// parse values
|
||||
get_token();
|
||||
continue;
|
||||
@@ -296,12 +380,14 @@ class parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
if (!overflow_error(sax, res, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, number_float_text(sax))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -347,7 +433,7 @@ class parser
|
||||
|
||||
case token_type::value_string:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -366,16 +452,63 @@ class parser
|
||||
case token_type::parse_error:
|
||||
{
|
||||
// using "uninitialized" to avoid an "expected" message
|
||||
return syntax_error(*sax, exception_message(token_type::uninitialized, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::uninitialized, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: keep what can be read of the token
|
||||
recover_token(allow_recovery);
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
// a string or a number
|
||||
continue;
|
||||
}
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// nothing could be read
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->null()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case token_type::end_of_input:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||
{
|
||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
||||
// there is nothing to recover
|
||||
static_cast<void>(report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(),
|
||||
"attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr), allow_recovery));
|
||||
return false;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover: the input ends where a value is missing
|
||||
if (states.empty())
|
||||
{
|
||||
// there is no value
|
||||
return false;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
@@ -385,7 +518,35 @@ class parser
|
||||
case token_type::literal_or_value:
|
||||
default: // the last token was unexpected
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::literal_or_value, "value"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (states.empty())
|
||||
{
|
||||
// look for the value after the garbage
|
||||
if (!skip_to_value(allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (last_token == token_type::name_separator)
|
||||
{
|
||||
// a stray ':'; the value may follow
|
||||
get_token();
|
||||
continue;
|
||||
}
|
||||
if (!recover_missing_value(sax, states, allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the state evaluation reads the token again
|
||||
unget_token(allow_recovery);
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -436,7 +597,30 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::end_array, "array"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_array, "array"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the array
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_object)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
// otherwise, a missing ',' (or a stray ':', which value
|
||||
// parsing drops): the next value begins here
|
||||
continue;
|
||||
}
|
||||
|
||||
// states.back() is false -> object
|
||||
@@ -453,10 +637,13 @@ class parser
|
||||
// parse key
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, false), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -464,7 +651,11 @@ class parser
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_token_expecting(token_type::name_separator)))
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::name_separator, "object separator"));
|
||||
if (!continue_after(key_error(sax, allow_recovery, true), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// parse values
|
||||
@@ -492,10 +683,546 @@ class parser
|
||||
continue;
|
||||
}
|
||||
|
||||
return syntax_error(*sax, exception_message(token_type::end_object, "object"));
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::end_object, "object"), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// recover
|
||||
if (last_token == token_type::end_of_input)
|
||||
{
|
||||
// the input ends inside the object
|
||||
return close_containers(sax, states, allow_recovery);
|
||||
}
|
||||
if (last_token == token_type::end_array)
|
||||
{
|
||||
// a wrong closing bracket closes the innermost container
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
states.pop_back();
|
||||
skip_to_state_evaluation = true;
|
||||
continue;
|
||||
}
|
||||
if (!continue_after(recover_member(sax, allow_recovery), skip_to_state_evaluation))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief continue sax_parse_internal() after a recovery
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
bool continue_after(const next_step step, bool& skip_to_state_evaluation)
|
||||
{
|
||||
if (step == next_step::evaluate_state)
|
||||
{
|
||||
// the state evaluation reads the token again
|
||||
m_lexer.unget_token();
|
||||
skip_to_state_evaluation = true;
|
||||
}
|
||||
return step != next_step::stop;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers: stop parsing
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type continue_after(std::false_type /*step*/, bool& /*skip_to_state_evaluation*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief parse an object key and the name separator (:) after it
|
||||
|
||||
last_token is the token where the key is expected. sax_parse_internal()
|
||||
repeats these steps rather than calling this function, which is used
|
||||
when recovering from an error.
|
||||
|
||||
@return next_step::parse_value if the value follows, with last_token its
|
||||
first token; next_step::evaluate_state if the object's state is
|
||||
to be evaluated after recovering from an error; next_step::stop
|
||||
to stop parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step parse_key(SAX* sax)
|
||||
{
|
||||
const std::true_type allow_recovery{};
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))
|
||||
{
|
||||
return key_error(sax, allow_recovery, false);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
|
||||
// parse separator (:)
|
||||
if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))
|
||||
{
|
||||
return key_error(sax, allow_recovery, true);
|
||||
}
|
||||
|
||||
// the value begins with the next token
|
||||
get_token();
|
||||
return next_step::parse_value;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a number that is too large for number_float_t, and recover
|
||||
from the error by passing the value on; the SAX parser gets the
|
||||
number's text as well
|
||||
|
||||
This is a separate function, as reading other numbers is measurably
|
||||
slower if the error is handled where they are read.
|
||||
|
||||
@param[in] sax the SAX parser
|
||||
@param[in] value the value that is not finite
|
||||
@return whether to continue parsing
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool overflow_error(SAX* sax, const number_float_t value, std::true_type allow_recovery)
|
||||
{
|
||||
if (!report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return sax->number_float(value, number_float_text(sax));
|
||||
}
|
||||
|
||||
/// @copydoc overflow_error
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type overflow_error(SAX* sax, const number_float_t /*value*/, std::false_type allow_recovery)
|
||||
{
|
||||
return report_error(sax, out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key; the parser for parse() and accept() never recovers
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
@return std::false_type, see report_error()
|
||||
*/
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type key_error(SAX* sax, std::false_type allow_recovery, const bool key_read)
|
||||
{
|
||||
return report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), key_read
|
||||
? exception_message(token_type::name_separator, "object separator")
|
||||
: exception_message(token_type::value_string, "object key"), nullptr), allow_recovery);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report a missing key, or a missing name separator (:) after the
|
||||
key, and recover from it
|
||||
|
||||
@param[in] key_read whether the key was read, so that the name separator
|
||||
is missing
|
||||
*/
|
||||
template<typename SAX>
|
||||
next_step key_error(SAX* sax, std::true_type allow_recovery, const bool key_read)
|
||||
{
|
||||
if (!key_read)
|
||||
{
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::value_string, "object key"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_key(sax);
|
||||
}
|
||||
|
||||
if (!report_error(sax, parse_error::create(exception_id::syntax_error, m_lexer.get_position(), exception_message(token_type::name_separator, "object separator"), nullptr), allow_recovery))
|
||||
{
|
||||
return next_step::stop;
|
||||
}
|
||||
return recover_name_separator(sax);
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// error recovery
|
||||
/////////////////////
|
||||
|
||||
/*
|
||||
The functions below repair an error after the SAX parser's parse_error()
|
||||
returned true (see #3989). Each mistake is repaired by the smallest local
|
||||
edit: a missing ',' or ':' is inserted, a stray token is removed, what can
|
||||
be read of an invalid string or number is kept (see
|
||||
lexer::recover_token()), a missing value becomes null, a wrong closing
|
||||
bracket closes the innermost container, and the end of the input closes
|
||||
all of them. The events stay balanced, and every key() is followed by
|
||||
exactly one value.
|
||||
|
||||
A repair hands a token to the state evaluation, by returning it to the
|
||||
lexer (lexer::unget_token()) so that the state evaluation reads it again,
|
||||
only if it is ',', ']', '}', or the end of the input. The state evaluation
|
||||
hands a token to value or key parsing only if it is none of them, so a
|
||||
token is never handed back and forth. Every other step reads a token or
|
||||
closes a container, so parsing always ends.
|
||||
*/
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser; the parser for parse() and
|
||||
accept() never recovers
|
||||
|
||||
@return std::false_type rather than false: its value is known where the
|
||||
function is called even if the call is not inlined, so the code
|
||||
for recovering is not generated
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type report_error(SAX* sax, const Exception& ex, std::false_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
error_reported = true;
|
||||
static_cast<void>(sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex));
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief report an error to the SAX parser
|
||||
@return whether to recover from the error
|
||||
*/
|
||||
template<typename SAX, typename Exception>
|
||||
bool report_error(SAX* sax, const Exception& ex, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
static_cast<void>(sax); // MSVC 2015 does not count calling a static parse_error() as using it
|
||||
const std::size_t position = m_lexer.get_position().chars_read_total;
|
||||
if (error_reported && position == last_error_position && last_token == last_error_token)
|
||||
{
|
||||
// a repair handed on the token of the error it repaired; the
|
||||
// token was reported already, and the SAX parser asked to recover
|
||||
return true;
|
||||
}
|
||||
|
||||
error_reported = true;
|
||||
last_error_position = position;
|
||||
last_error_token = last_token;
|
||||
|
||||
if (!sax->parse_error(m_lexer.get_position(), m_lexer.get_token_string(), ex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// the token string of the next error begins here
|
||||
m_lexer.restart_token_string();
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief keep what can be read of the token that the lexer rejected
|
||||
|
||||
The error was reported for the rejected token, so it is not reported again
|
||||
for the token it is repaired to (see lexer::recover_token()).
|
||||
*/
|
||||
token_type recover_token()
|
||||
{
|
||||
last_token = m_lexer.recover_token();
|
||||
last_error_position = m_lexer.get_position().chars_read_total;
|
||||
last_error_token = last_token;
|
||||
return last_token;
|
||||
}
|
||||
|
||||
// The functions below are called from sax_parse_internal() after an error
|
||||
// was reported. The parser for parse() and accept() stopped there, so for
|
||||
// it they are stubs: the code for recovering is then not even referenced,
|
||||
// and unoptimized builds do not emit it either.
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
token_type recover_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
return recover_token();
|
||||
}
|
||||
|
||||
/// @copydoc recover_token()
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_token(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// return the token that was read last to the lexer (see lexer::unget_token())
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
void unget_token(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
m_lexer.unget_token();
|
||||
}
|
||||
|
||||
/// @copydoc unget_token(std::true_type)
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static void unget_token(std::false_type /*allow_recovery*/) noexcept {}
|
||||
|
||||
/// @copydoc skip_to_value
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type skip_to_value(std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// @copydoc recover_missing_value
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type recover_missing_value(SAX* /*sax*/, const std::vector<bool>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// pass the end events of all open containers
|
||||
template<typename SAX>
|
||||
bool close_containers(SAX* sax, std::vector<bool>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (!states.empty())
|
||||
{
|
||||
const bool is_array = states.back();
|
||||
states.pop_back();
|
||||
if (JSON_HEDLEY_UNLIKELY(is_array ? !sax->end_array() : !sax->end_object()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// @copydoc close_containers
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
static std::false_type close_containers(SAX* /*sax*/, std::vector<bool>& /*states*/, std::false_type /*allow_recovery*/) noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read tokens until one begins a value, skipping everything before
|
||||
the top-level value
|
||||
@return whether a value begins with last_token
|
||||
*/
|
||||
bool skip_to_value(std::true_type /*allow_recovery*/)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
switch (get_token())
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::literal_true:
|
||||
case token_type::value_float:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
return true;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return false;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token != token_type::uninitialized)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::value_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip the rest of an object member that cannot be read
|
||||
|
||||
Reads tokens, beginning with last_token, until a ',', '}', or ']' that is
|
||||
not inside a container that begins in the skipped tokens, or the end of
|
||||
the input.
|
||||
*/
|
||||
void skip_member()
|
||||
{
|
||||
std::size_t depth = 0;
|
||||
while (true)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
++depth;
|
||||
break;
|
||||
|
||||
case token_type::end_array:
|
||||
case token_type::end_object:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
--depth;
|
||||
break;
|
||||
|
||||
case token_type::value_separator:
|
||||
if (depth == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case token_type::end_of_input:
|
||||
return;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
break;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
get_token();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a value where it is missing
|
||||
|
||||
last_token is ',', ']', '}', or the end of the input, where a value was
|
||||
expected. In an object, the key gets null; in an array, a ',' where a
|
||||
value is missing stands for null (as in JavaScript), while an array that
|
||||
ends there just ends.
|
||||
*/
|
||||
template<typename SAX>
|
||||
bool recover_missing_value(SAX* sax, const std::vector<bool>& states, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
JSON_ASSERT(!states.empty());
|
||||
if (!states.back() || last_token == token_type::value_separator)
|
||||
{
|
||||
return sax->null();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// recover from a missing key; last_token is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_key(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// no member: the object's state handles the token
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::parse_error:
|
||||
recover_token();
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a key that could be repaired
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a member without a key
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a missing name separator (:) after the key; last_token
|
||||
/// is where it was expected
|
||||
template<typename SAX>
|
||||
next_step recover_name_separator(SAX* sax)
|
||||
{
|
||||
switch (last_token)
|
||||
{
|
||||
case token_type::value_separator:
|
||||
case token_type::end_object:
|
||||
case token_type::end_array:
|
||||
case token_type::end_of_input:
|
||||
// the value is missing as well
|
||||
return sax->null() ? next_step::evaluate_state : next_step::stop;
|
||||
|
||||
case token_type::uninitialized:
|
||||
case token_type::literal_true:
|
||||
case token_type::literal_false:
|
||||
case token_type::literal_null:
|
||||
case token_type::value_string:
|
||||
case token_type::value_unsigned:
|
||||
case token_type::value_integer:
|
||||
case token_type::value_float:
|
||||
case token_type::begin_array:
|
||||
case token_type::begin_object:
|
||||
case token_type::name_separator:
|
||||
case token_type::parse_error:
|
||||
case token_type::literal_or_value:
|
||||
default:
|
||||
// a missing ':'; the value begins here
|
||||
return next_step::parse_value;
|
||||
}
|
||||
}
|
||||
|
||||
/// recover from a token after an object member that is neither ',' nor
|
||||
/// '}' (nor ']' or the end of the input, which the caller handles)
|
||||
template<typename SAX>
|
||||
next_step recover_member(SAX* sax, std::true_type /*allow_recovery*/)
|
||||
{
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
recover_token();
|
||||
}
|
||||
if (last_token == token_type::value_string)
|
||||
{
|
||||
// a missing ','; the next key begins here
|
||||
return parse_key(sax);
|
||||
}
|
||||
skip_member();
|
||||
return next_step::evaluate_state;
|
||||
}
|
||||
|
||||
/// the parser for parse() and accept() never recovers (and does not come
|
||||
/// here, as report_error() returned false)
|
||||
template<typename SAX>
|
||||
JSON_INTERNAL_ALWAYS_INLINE
|
||||
std::false_type recover_member(SAX* /*sax*/, std::false_type /*allow_recovery*/) const noexcept
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
/// get next token from lexer
|
||||
token_type get_token()
|
||||
{
|
||||
@@ -566,6 +1293,12 @@ class parser
|
||||
const bool allow_exceptions = true;
|
||||
/// whether trailing commas in objects and arrays should be ignored (true) or signaled as errors (false)
|
||||
const bool ignore_trailing_commas = false;
|
||||
/// whether an error was reported to the SAX parser
|
||||
bool error_reported = false;
|
||||
/// the position of the last reported error
|
||||
std::size_t last_error_position = 0;
|
||||
/// the token of the last reported error
|
||||
token_type last_error_token = token_type::uninitialized;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -532,7 +532,7 @@ class json_pointer
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<slot> slots;
|
||||
std::vector<slot> table;
|
||||
std::size_t used = 0;
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
@@ -547,19 +547,19 @@ class json_pointer
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(slots.size() * 2);
|
||||
old.swap(slots);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
std::vector<slot> old(table.size() * 2);
|
||||
old.swap(table);
|
||||
const std::size_t mask = table.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
while (table[i].token != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
slots[i] = s;
|
||||
table[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -572,7 +572,7 @@ class json_pointer
|
||||
|
||||
// start with the empty prefix only
|
||||
prefix_tree()
|
||||
: slots(16)
|
||||
: table(16)
|
||||
, parent(1, 0)
|
||||
, is_array(1, false)
|
||||
{}
|
||||
@@ -582,20 +582,20 @@ class json_pointer
|
||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||
{
|
||||
const std::size_t hash = hash_of(id, reference_token);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
const std::size_t mask = table.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
while (table[i].token != nullptr)
|
||||
{
|
||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||
if (table[i].hash == hash && table[i].parent == id && *table[i].token == reference_token)
|
||||
{
|
||||
return slots[i].id;
|
||||
return table[i].id;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
// new prefix
|
||||
const std::size_t child = parent.size();
|
||||
slots[i] = slot(hash, id, &reference_token, child);
|
||||
table[i] = slot(hash, id, &reference_token, child);
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
@@ -604,7 +604,7 @@ class json_pointer
|
||||
is_array.push_back(false);
|
||||
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > slots.size())
|
||||
if (++used * 2 > table.size())
|
||||
{
|
||||
grow();
|
||||
}
|
||||
|
||||
@@ -199,6 +199,17 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Inlines small functions even in unoptimized builds, so that they are not
|
||||
// emitted. The parsers for parse(), accept(), and from_*() use it for the
|
||||
// functions that stand in for the code recovering from errors (see #3989).
|
||||
// MSVC is left to decide, as it warns (C4714) where it does not inline a
|
||||
// __forceinline function.
|
||||
#if defined(_MSC_VER) && !defined(__clang__)
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE
|
||||
#else
|
||||
#define JSON_INTERNAL_ALWAYS_INLINE JSON_HEDLEY_ALWAYS_INLINE
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||
#undef JSON_INLINE_VARIABLE
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_INTERNAL_ALWAYS_INLINE
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||
#undef JSON_UNLOCKED_FILE_IO
|
||||
#undef JSON_DTOA_SSE2
|
||||
#undef JSON_DTOA_NEON
|
||||
|
||||
@@ -47,6 +49,8 @@
|
||||
#undef JSON_STRICT_BINARY_UTF8
|
||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
||||
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/input/binary_reader.hpp>
|
||||
#include <nlohmann/detail/input/string_scan.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
@@ -204,24 +205,12 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_cbor_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -256,13 +245,7 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
}
|
||||
write_cbor_array(j, depth);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -420,54 +403,12 @@ class binary_writer
|
||||
}
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
// one case for both, so that an array mixing negative
|
||||
// (number_integer) and non-negative (number_unsigned) values
|
||||
// does not mispredict the switch
|
||||
write_msgpack_integer(j, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -485,13 +426,7 @@ class binary_writer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
// step 1: write control byte and the array size
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
write_msgpack_array(j, depth);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -824,6 +759,142 @@ class binary_writer
|
||||
JSON_THROW(type_error::create(exception_id::discarded_value_used, concat("cannot serialize discarded value to ", format_name), &j));
|
||||
}
|
||||
|
||||
/// how the range of integer type @a NumberType relates to that of @a TargetType
|
||||
template<typename TargetType, typename NumberType>
|
||||
struct integer_range
|
||||
{
|
||||
/// whether NumberType can hold values above the maximum of TargetType
|
||||
static constexpr bool may_exceed_max = std::numeric_limits<NumberType>::digits > std::numeric_limits<TargetType>::digits;
|
||||
/// whether NumberType can hold values below the minimum of TargetType
|
||||
static constexpr bool may_fall_below_min = std::numeric_limits<NumberType>::is_signed
|
||||
&& (!std::numeric_limits<TargetType>::is_signed || may_exceed_max);
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n does not exceed the maximum of @a TargetType
|
||||
|
||||
The binary formats store integers in at most 64 bits, but number_integer_t
|
||||
and number_unsigned_t may be wider (e.g., __int128). The range of the
|
||||
integer is taken from std::numeric_limits, so a number type whose range
|
||||
fits into @a TargetType is never checked: the function is then constant
|
||||
true, and the default int64_t/uint64_t types pay nothing.
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_max<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_exceed_max>());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType /*unused*/, std::false_type /*may_exceed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_max(const NumberType n, std::true_type /*may_exceed*/) noexcept
|
||||
{
|
||||
// NumberType has more digits than TargetType, so it can hold its maximum
|
||||
return n <= static_cast<NumberType>((std::numeric_limits<TargetType>::max)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n is not below the minimum of @a TargetType;
|
||||
constant true if NumberType cannot hold such a value
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n, std::integral_constant<bool, integer_range<TargetType, NumberType>::may_fall_below_min>());
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType /*unused*/, std::false_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits_min(const NumberType n, std::true_type /*may_fall_below*/) noexcept
|
||||
{
|
||||
// NumberType is signed and either TargetType is unsigned (minimum 0)
|
||||
// or NumberType has more digits, so it can hold the minimum
|
||||
return n >= static_cast<NumberType>((std::numeric_limits<TargetType>::min)());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether integer @a n lies in the range of @a TargetType
|
||||
*/
|
||||
template<typename TargetType, typename NumberType>
|
||||
static constexpr bool integer_fits(const NumberType n) noexcept
|
||||
{
|
||||
return integer_fits_min<TargetType>(n) && integer_fits_max<TargetType>(n);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the non-negative integer @a n
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static std::string integer_to_string(NumberType n, std::false_type /*is_signed*/)
|
||||
{
|
||||
std::string result;
|
||||
do
|
||||
{
|
||||
result.push_back(static_cast<char>('0' + (n % 10)));
|
||||
n = static_cast<NumberType>(n / 10);
|
||||
}
|
||||
while (n != 0);
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||
is negative
|
||||
*/
|
||||
template<typename NumberType>
|
||||
static std::string integer_to_string(NumberType n, std::true_type /*is_signed*/)
|
||||
{
|
||||
// the remainder of a negative n is not positive; each digit is
|
||||
// negated on its own, because negating the minimum of NumberType
|
||||
// as a whole would overflow
|
||||
const bool negative = n < 0;
|
||||
std::string result;
|
||||
do
|
||||
{
|
||||
const auto remainder = n % 10;
|
||||
result.push_back(static_cast<char>('0' + (remainder < 0 ? -remainder : remainder)));
|
||||
n = static_cast<NumberType>(n / 10);
|
||||
}
|
||||
while (n != 0);
|
||||
if (negative)
|
||||
{
|
||||
result.push_back('-');
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format_name
|
||||
@throw out_of_range.407 always
|
||||
*/
|
||||
JSON_HEDLEY_NO_RETURN static void throw_integer_out_of_range(const BasicJsonType& j, const char* format_name, const char* range)
|
||||
{
|
||||
// neither std::to_string(), which has no overload for integer types
|
||||
// wider than 64 bits, nor dump(), which would also instantiate the
|
||||
// serializer for objects and fail for key types it cannot write
|
||||
using integer_limits = std::numeric_limits<typename BasicJsonType::number_integer_t>;
|
||||
using unsigned_limits = std::numeric_limits<typename BasicJsonType::number_unsigned_t>;
|
||||
const std::string number = j.is_number_unsigned()
|
||||
? integer_to_string(j.m_data.m_value.number_unsigned, std::integral_constant<bool, unsigned_limits::is_signed>())
|
||||
: integer_to_string(j.m_data.m_value.number_integer, std::integral_constant<bool, integer_limits::is_signed>());
|
||||
static_cast<void>(number); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(format_name);
|
||||
static_cast<void>(range);
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", number, " cannot be represented by ", format_name, " as it does not fit ", range), &j));
|
||||
}
|
||||
|
||||
void write_msgpack_array_prefix(const std::size_t N, const BasicJsonType& j)
|
||||
{
|
||||
const auto n = to_msgpack_length(N, j);
|
||||
@@ -1580,6 +1651,8 @@ class binary_writer
|
||||
is neither an object nor an array
|
||||
@throw out_of_range.415 if @a j is binary with a subtype that does not fit
|
||||
into a byte, before anything is written
|
||||
@throw out_of_range.407 if @a j is an integer that does not fit the 64 bits
|
||||
of a BSON integer, before anything is written
|
||||
@throw type_error.316 if @a j is a string that is not valid UTF-8, before
|
||||
anything is written
|
||||
@throw type_error.321 if @a j is discarded
|
||||
@@ -1598,9 +1671,17 @@ class binary_writer
|
||||
return 8ul;
|
||||
|
||||
case value_t::number_integer:
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "int64");
|
||||
}
|
||||
return calc_bson_integer_size(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BSON", "uint64");
|
||||
}
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
@@ -1639,8 +1720,9 @@ class binary_writer
|
||||
case value_t::number_float:
|
||||
return write_bson_double(name, j.m_data.m_value.number_float);
|
||||
|
||||
// calc_bson_value_size() checked that integers fit 64 bits
|
||||
case value_t::number_integer:
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
return write_bson_integer(name, static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
@@ -1951,6 +2033,376 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief size class of an unsigned integer from its bit length
|
||||
|
||||
1, 2, 3, or 4 if @a x fits 1, 2, 4, or 8 bytes. Looked up from the number
|
||||
of leading zero bits instead of found by branches: the sizes of a stream
|
||||
of numbers are hard to predict, and a mispredicted branch costs more than
|
||||
writing the number. (A table, as comparisons are vectorized by clang into
|
||||
slower code.)
|
||||
*/
|
||||
static unsigned integer_size_class(const std::uint64_t x) noexcept
|
||||
{
|
||||
static constexpr std::array<std::uint8_t, 64> size_class_by_leading_zeros =
|
||||
{
|
||||
{
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
}
|
||||
};
|
||||
// x | 1 keeps count_leading_zeros() away from 0
|
||||
return size_class_by_leading_zeros[static_cast<std::size_t>(count_leading_zeros(x | 1u))];
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode @a first_byte and the low-order bytes of @a value that
|
||||
@a size_class calls for (none, or 1, 2, 4, or 8) in network order
|
||||
to @a out
|
||||
@return the number of characters of the encoding; up to
|
||||
@ref integer_encoding_space characters at @a out are overwritten
|
||||
|
||||
The bytes are written as one byte and one 8-byte word whose bytes after
|
||||
the value are scratch that the caller drops, so that writing them does not
|
||||
depend on the size class. For class 0 the word is all scratch; it is
|
||||
shifted by 60 bits rather than 64, which would be undefined.
|
||||
*/
|
||||
std::size_t encode_first_byte_and_tail(CharType* out, const std::uint8_t first_byte, const unsigned size_class, const std::uint64_t value) const noexcept
|
||||
{
|
||||
out[0] = to_char_type(first_byte);
|
||||
const unsigned unused_bits = 64u - ((8u << size_class) >> 1u);
|
||||
const std::uint64_t tail = is_little_endian ? byte_swap(value) >> unused_bits : value << unused_bits;
|
||||
std::memcpy(out + 1, &tail, sizeof(tail));
|
||||
return ((std::size_t{1} << size_class) >> 1u) + 1;
|
||||
}
|
||||
|
||||
/// characters encode_first_byte_and_tail() may overwrite: the first byte
|
||||
/// and one 8-byte word
|
||||
static constexpr std::size_t integer_encoding_space = 9;
|
||||
|
||||
/*!
|
||||
@brief the 64 bits of a number_integer or number_unsigned value, and a mask
|
||||
that is all ones if it is negative, else zero
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
const bool is_signed = j.m_data.m_type == value_t::number_integer;
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
return raw;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief encode the head of a CBOR integer to @a out without branching on its
|
||||
size
|
||||
@return the number of characters of the head
|
||||
|
||||
Same output as @ref write_cbor_head. The lengths of strings and containers
|
||||
are mostly predictable, so their heads keep the branches; the sizes of a
|
||||
run of integers are not, so an integer's head is assembled without them.
|
||||
*/
|
||||
std::size_t encode_cbor_integer_head(CharType* out, const std::uint8_t major_type, const std::uint64_t argument) const noexcept
|
||||
{
|
||||
// an argument of at most 23 is in size class 1 (one byte) and is moved
|
||||
// to class 0, the first byte, by subtracting 1
|
||||
const auto is_immediate = static_cast<unsigned>(argument <= 0x17u);
|
||||
const unsigned size_class = integer_size_class(argument) - is_immediate;
|
||||
|
||||
// the first byte: the argument itself if it is immediate, else
|
||||
// 0x17 + size_class, selected through a mask
|
||||
const std::uint64_t immediate_mask = std::uint64_t{0} - is_immediate;
|
||||
const auto first_byte = static_cast<std::uint8_t>(major_type + ((argument & immediate_mask) | ((0x17u + size_class) & ~immediate_mask)));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, argument);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as CBOR
|
||||
|
||||
Integer elements, the most common scalar, are written without a call to
|
||||
@ref write_cbor. The loop is kept out of line: inlined into write_cbor, its
|
||||
registers would be saved and restored on every call of write_cbor, which
|
||||
is one call per value nested in an object.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||
write_cbor_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_cbor_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_cbor_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_cbor(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the elements of the array @a j, runs of integers with
|
||||
@a encode_integer and all other values with @a write_value
|
||||
|
||||
Runs of integers are encoded straight into space reserved from the sink
|
||||
for a whole run: the write pointer stays a local, where it would otherwise
|
||||
be reloaded from the sink for every value, as a store of a character may
|
||||
alias it.
|
||||
*/
|
||||
template<typename EncodeInteger, typename WriteValue>
|
||||
void write_array_elements(const BasicJsonType& j, EncodeInteger encode_integer, WriteValue write_value)
|
||||
{
|
||||
constexpr std::size_t max_run = max_reserve_size / integer_encoding_space;
|
||||
auto it = j.m_data.m_value.array->begin();
|
||||
const auto end = j.m_data.m_value.array->end();
|
||||
while (it != end)
|
||||
{
|
||||
if (it->is_number_integer())
|
||||
{
|
||||
CharType* const start = oa.reserve(max_run * integer_encoding_space);
|
||||
CharType* out = start;
|
||||
for (std::size_t n = 0; n < max_run && it != end && it->is_number_integer(); ++n, ++it)
|
||||
{
|
||||
out += encode_integer(out, *it);
|
||||
}
|
||||
oa.commit(static_cast<std::size_t>(out - start));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_value(*it);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a CBOR integer
|
||||
|
||||
A negative integer n has major type 1 and the argument -1 - n, which is ~n.
|
||||
Kept out of line, like the other per-value paths, so that write_cbor stays
|
||||
small (runs of integers in an array are encoded inline instead).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_cbor_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a CBOR integer, with range checks on the whole value
|
||||
void write_cbor_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "uint64");
|
||||
}
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
// CBOR does not differentiate between positive signed
|
||||
// integers and unsigned integers
|
||||
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// a negative integer n is encoded as -1 - n
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(-1 - j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "CBOR", "[-2^64, 2^64-1]");
|
||||
}
|
||||
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_cbor_integer) and return the number of characters
|
||||
std::size_t encode_cbor_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
// ~raw is -1 - n in the 64 bits that are written
|
||||
return encode_cbor_integer_head(out, static_cast<std::uint8_t>(sign_mask & 0x20u), raw ^ sign_mask);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether the CBOR and MessagePack integers of BasicJsonType are
|
||||
written by @ref encode_cbor_integer and @ref encode_msgpack_integer
|
||||
|
||||
These pick the format from the low 64 bits; a wider number type is written
|
||||
with range checks on the whole value instead.
|
||||
*/
|
||||
static constexpr bool integers_fit_64_bits() noexcept
|
||||
{
|
||||
return sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
&& sizeof(typename BasicJsonType::number_unsigned_t) <= sizeof(std::uint64_t);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a number_integer or number_unsigned value as a MessagePack
|
||||
integer without branching on its sign or size
|
||||
|
||||
Same output as @ref write_msgpack: a non-negative integer as positive fixint
|
||||
(< 128) or uint 8/16/32/64, a negative one as negative fixint (>= -32) or
|
||||
int 8/16/32/64. Kept out of line (see @ref write_cbor_integer).
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
oa.commit(encode_msgpack_integer(oa.reserve(integer_encoding_space), j));
|
||||
}
|
||||
|
||||
/// encode a number_integer or number_unsigned value to @a out (see
|
||||
/// @ref write_msgpack_integer) and return the number of characters
|
||||
std::size_t encode_msgpack_integer(CharType* out, const BasicJsonType& j) const noexcept
|
||||
{
|
||||
std::uint64_t sign_mask = 0;
|
||||
const std::uint64_t raw = integer_bits(j, sign_mask);
|
||||
|
||||
// A negative n fits int 8/16/32 exactly if 2 * (-1 - n) + 1 fits
|
||||
// uint 8/16/32, so x gives the size class of either sign.
|
||||
const std::uint64_t is_negative = sign_mask & 1u;
|
||||
const std::uint64_t x = ((raw ^ sign_mask) << is_negative) | is_negative;
|
||||
|
||||
// A fixint is at most 127, or at least -32 (x at most 63). It is in
|
||||
// size class 1 and is moved to class 0, the first byte, by subtracting 1.
|
||||
const auto is_fixint = static_cast<unsigned>(x <= (127u ^ (sign_mask & 64u)));
|
||||
const unsigned size_class = integer_size_class(x) - is_fixint;
|
||||
|
||||
// the first byte: the low byte of the integer for a fixint, else the
|
||||
// marker 0xCC..0xCF (uint) or 0xD0..0xD3 (int), selected through a mask
|
||||
const std::uint64_t fixint_mask = std::uint64_t{0} - is_fixint;
|
||||
const std::uint64_t marker = 0xCBu + size_class + (sign_mask & 4u);
|
||||
const auto first_byte = static_cast<std::uint8_t>((raw & fixint_mask) | (marker & ~fixint_mask));
|
||||
return encode_first_byte_and_tail(out, first_byte, size_class, raw);
|
||||
}
|
||||
|
||||
/// write a number_integer or number_unsigned value of a number type wider
|
||||
/// than 64 bits as a MessagePack integer, with range checks on the whole value
|
||||
void write_msgpack_integer(const BasicJsonType& j, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
if (j.m_data.m_type == value_t::number_unsigned)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "uint64");
|
||||
}
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
return;
|
||||
}
|
||||
|
||||
if (j.m_data.m_value.number_integer >= 0)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::uint64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
// MessagePack does not differentiate between positive
|
||||
// signed integers and unsigned integers.
|
||||
write_msgpack_unsigned(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_min<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "MessagePack", "[-2^63, 2^64-1]");
|
||||
}
|
||||
if (j.m_data.m_value.number_integer >= -32)
|
||||
{
|
||||
// negative fixnum
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())
|
||||
{
|
||||
// int 8
|
||||
oa.write_character(to_char_type(0xD0));
|
||||
write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())
|
||||
{
|
||||
// int 16
|
||||
oa.write_character(to_char_type(0xD1));
|
||||
write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&
|
||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())
|
||||
{
|
||||
// int 32
|
||||
oa.write_character(to_char_type(0xD2));
|
||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// int 64
|
||||
oa.write_character(to_char_type(0xD3));
|
||||
write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an array value (head and elements) as MessagePack
|
||||
|
||||
See @ref write_cbor_array.
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_array(const BasicJsonType& j, const std::size_t depth)
|
||||
{
|
||||
write_msgpack_array_prefix(j.m_data.m_value.array->size(), j);
|
||||
write_msgpack_array_elements(j, depth, std::integral_constant<bool, integers_fit_64_bits()>());
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::true_type /*fits_64_bits*/)
|
||||
{
|
||||
write_array_elements(j, [this](CharType * out, const BasicJsonType & el)
|
||||
{
|
||||
return encode_msgpack_integer(out, el);
|
||||
}, [this, depth](const BasicJsonType & el)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
});
|
||||
}
|
||||
|
||||
void write_msgpack_array_elements(const BasicJsonType& j, const std::size_t depth, std::false_type /*fits_64_bits*/)
|
||||
{
|
||||
for (const auto& el : *j.m_data.m_value.array)
|
||||
{
|
||||
write_msgpack(el, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write an object key as a CBOR text string
|
||||
|
||||
@@ -2147,7 +2599,7 @@ class binary_writer
|
||||
{
|
||||
return 'L';
|
||||
}
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value)
|
||||
if (use_bjdata && std::is_unsigned<NumberType>::value && integer_fits_max<std::uint64_t>(n))
|
||||
{
|
||||
return 'M';
|
||||
}
|
||||
@@ -2272,14 +2724,16 @@ class binary_writer
|
||||
@brief checks whether a JSON number fits into @a TargetType
|
||||
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||
@return whether @a el's value can be represented by @a TargetType without
|
||||
wrapping, regardless of which of the two kinds it is stored as
|
||||
wrapping, regardless of which of the two kinds it is stored as;
|
||||
false for a value that does not even fit the 64-bit type it is
|
||||
read as (possible for number types wider than 64 bits)
|
||||
*/
|
||||
template<typename TargetType>
|
||||
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||
{
|
||||
return el.is_number_unsigned()
|
||||
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
? integer_fits_max<std::uint64_t>(el.m_data.m_value.number_unsigned) && value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||
: integer_fits<std::int64_t>(el.m_data.m_value.number_integer) && value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2511,10 +2965,11 @@ class binary_writer
|
||||
for (const auto& el : dims)
|
||||
{
|
||||
// a dimension is read as an unsigned value below, so anything that
|
||||
// is not a non-negative integer is rejected: a non-integer entry
|
||||
// would pun unrelated bytes as the dimension, and a negative one
|
||||
// would wrap into a nonsensical length
|
||||
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
|
||||
// is not a non-negative integer in the range of std::uint64_t is
|
||||
// rejected: a non-integer entry would pun unrelated bytes as the
|
||||
// dimension, and a negative or wider one would wrap into a
|
||||
// nonsensical length
|
||||
if (!el.is_number_integer() || !bjdata_ndarray_value_in_range<std::uint64_t>(el))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2646,9 +3101,9 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
if (j.m_data.m_value.number_unsigned > static_cast<typename BasicJsonType::number_unsigned_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(j.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(exception_id::integer_too_large, concat("integer number ", std::to_string(j.m_data.m_value.number_unsigned), " cannot be represented by BON8 as it does not fit int64"), &j));
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
@@ -2657,6 +3112,10 @@ class binary_writer
|
||||
|
||||
case value_t::number_integer:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(j.m_data.m_value.number_integer)))
|
||||
{
|
||||
throw_integer_out_of_range(j, "BON8", "int64");
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(j.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // conditional, integral_constant, is_same
|
||||
@@ -48,32 +51,170 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// the largest number of characters a writer may reserve() from a sink at once;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector.
|
||||
///
|
||||
/// Most writes are a few bytes - a type marker, or a number of 1, 2, 4, or 8
|
||||
/// bytes - and appending those with push_back() or insert() cost more than
|
||||
/// encoding them: insert() is an out-of-line call into a routine that can insert
|
||||
/// anywhere, and resize() is one as well with libstdc++. The sink therefore keeps
|
||||
/// its own write position: it grows the vector geometrically ahead of the
|
||||
/// position and copies each write to it, so a write is a capacity check and a
|
||||
/// short copy. The destructor shrinks the vector to the bytes actually written,
|
||||
/// also when a write throws; shrinking a vector never allocates. The sink owns
|
||||
/// that trim, so it can be moved but not copied, and the vector must not be used
|
||||
/// while the sink is alive.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(&vec), position(vec.size())
|
||||
{}
|
||||
|
||||
output_vector_sink(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(const output_vector_sink&) = delete;
|
||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||
|
||||
output_vector_sink(output_vector_sink&& other) noexcept
|
||||
: v(other.v), position(other.position)
|
||||
{
|
||||
other.v = nullptr;
|
||||
}
|
||||
|
||||
~output_vector_sink()
|
||||
{
|
||||
if (v != nullptr)
|
||||
{
|
||||
v->resize(position);
|
||||
}
|
||||
}
|
||||
|
||||
// always inlined: GCC otherwise keeps these out of line in a large
|
||||
// translation unit, and a call per byte or number costs more than the write
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_character(CharType c)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
||||
{
|
||||
grow(1);
|
||||
}
|
||||
(*v)[position++] = c;
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values; memcpy must not
|
||||
// see it.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
if (length >= max_reserve_size)
|
||||
{
|
||||
append(s, length);
|
||||
return;
|
||||
}
|
||||
grow(length);
|
||||
}
|
||||
if (length != 0)
|
||||
{
|
||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
||||
position += length;
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief space for writing up to @a length characters directly
|
||||
///
|
||||
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
|
||||
/// writable characters at the write position; @ref commit then keeps the
|
||||
/// first ones of them. A writer can store whole words there and keep only
|
||||
/// the bytes it needs, and keep its write pointer in a register across many
|
||||
/// values instead of updating the sink after each.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
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
|
||||
{
|
||||
position += length;
|
||||
}
|
||||
|
||||
private:
|
||||
/// 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
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void grow(const std::size_t length)
|
||||
{
|
||||
// use the capacity the vector already has, else at least double it;
|
||||
// never less than 64 characters, so that a small value does not
|
||||
// reallocate for every few bytes
|
||||
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)));
|
||||
}
|
||||
|
||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||
/// the space ahead of the position: insert() copies them once into the
|
||||
/// grown vector, where grow() would first zero the new space and then copy
|
||||
/// over it - noticeable for a large string or binary value
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void append(const CharType* s, const std::size_t 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, position + length + max_reserve_size));
|
||||
}
|
||||
v->insert(v->end(), s, s + length);
|
||||
position = v->size();
|
||||
}
|
||||
|
||||
std::vector<CharType, AllocatorType>* v;
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c)
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
// pointer with length 0 for empty strings/binary values; appending an empty
|
||||
// range is a no-op
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -82,33 +223,6 @@ class output_vector_sink
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -188,8 +302,24 @@ class output_adapter_sink
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
}
|
||||
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
{
|
||||
oa->write_characters(reserved.data(), length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
@@ -1525,7 +1525,7 @@ class serializer
|
||||
// without leading zeros
|
||||
if (abs_value < 10000000000000000u)
|
||||
{
|
||||
const std::uint64_t value = abs_value;
|
||||
const auto value = static_cast<std::uint64_t>(abs_value); // fits: abs_value < 10^16
|
||||
const std::uint64_t upper = value / 100000000u;
|
||||
const std::uint64_t first = dtoa_impl::eight_digit_bytes(upper != 0 ? upper : value);
|
||||
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // is_signed
|
||||
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
@@ -208,13 +207,16 @@ FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noe
|
||||
|
||||
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||
// power of two that the float represents exactly
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||
// power of two that the float represents exactly; the signedness comes
|
||||
// from numeric_limits as well, because std::is_signed is false for class
|
||||
// types such as 128-bit or multiprecision integers
|
||||
using limits = std::numeric_limits<IntegerType>;
|
||||
const FloatType bound = std::ldexp(static_cast<FloatType>(1), limits::digits);
|
||||
if (f >= bound)
|
||||
{
|
||||
return ordered(-1);
|
||||
}
|
||||
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
if (limits::is_signed ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||
{
|
||||
return ordered(1);
|
||||
}
|
||||
|
||||
@@ -422,8 +422,10 @@ class builder
|
||||
arena_ensure(16);
|
||||
std::memcpy(m_arena_write, r, 16);
|
||||
}
|
||||
else
|
||||
else if (n != 0)
|
||||
{
|
||||
// (an empty run leaves an empty arena unallocated, and memcpy()
|
||||
// must not be passed its null pointer even for zero bytes)
|
||||
arena_ensure(n);
|
||||
std::memcpy(m_arena_write, r, n);
|
||||
}
|
||||
@@ -682,7 +684,7 @@ obj_next:
|
||||
return fail(error_code::expected_object_end);
|
||||
|
||||
close_object:
|
||||
// a large object gets a hash index (objects only, so that closing
|
||||
// a large object gets a hash index for its keys (objects only, so that closing
|
||||
// an array pays nothing for this)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
|
||||
{
|
||||
@@ -844,7 +846,7 @@ indent_done:
|
||||
|
||||
/// append a node: (kind, flags, extra, off) and the second word (len, or
|
||||
/// an integer's value); two stores on little-endian targets
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE node* emit(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(value_t k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
|
||||
{
|
||||
@@ -874,9 +876,9 @@ indent_done:
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void open(value_t k)
|
||||
{
|
||||
// (base is read after emit(), which moves the node array when it
|
||||
// (base is read after push_node(), which moves the node array when it
|
||||
// grows; in one expression the order of the two is unspecified)
|
||||
const node* const n = emit(k, 0, 0, static_cast<std::size_t>(p - b), 0);
|
||||
const node* const n = push_node(k, 0, 0, static_cast<std::size_t>(p - b), 0);
|
||||
const auto idx = static_cast<std::uint32_t>(n - base);
|
||||
if (depth != 0)
|
||||
{
|
||||
@@ -936,7 +938,7 @@ indent_done:
|
||||
{
|
||||
return fail(error_code::invalid_literal);
|
||||
}
|
||||
emit(k, flags, 0, static_cast<std::size_t>(p - b), n);
|
||||
push_node(k, flags, 0, static_cast<std::size_t>(p - b), n);
|
||||
p += n;
|
||||
return true;
|
||||
}
|
||||
@@ -1044,7 +1046,7 @@ indent_done:
|
||||
// of the negative std::int64_t)
|
||||
second = negative ? 0 - m : m;
|
||||
}
|
||||
emit(kind, 0, layout, static_cast<std::size_t>(s - b), second);
|
||||
push_node(kind, 0, layout, static_cast<std::size_t>(s - b), second);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1057,7 +1059,7 @@ indent_done:
|
||||
p = scan_string_run<Value>(p, e);
|
||||
if (NLOHMANN_VIEW_LIKELY(p != e && *p == '"'))
|
||||
{
|
||||
emit(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||
push_node(value_t::string, 0, 0, static_cast<std::size_t>(s - b), static_cast<std::uint64_t>(p - s));
|
||||
++p;
|
||||
return true;
|
||||
}
|
||||
@@ -1067,7 +1069,7 @@ indent_done:
|
||||
return false;
|
||||
}
|
||||
p = r.p;
|
||||
emit(value_t::string, node_flags::escaped, 0, r.start, r.len);
|
||||
push_node(value_t::string, node_flags::escaped, 0, r.start, r.len);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -201,6 +201,11 @@ class json_side
|
||||
const BasicJsonType* m_json;
|
||||
};
|
||||
|
||||
inline bool is_number(value_t t) noexcept
|
||||
{
|
||||
return t == value_t::number_integer || t == value_t::number_unsigned || t == value_t::number_float;
|
||||
}
|
||||
|
||||
/// whether two sides are equal; iterative, so that the nesting depth is
|
||||
/// limited by memory only
|
||||
template<typename BasicJsonType, typename A, typename B>
|
||||
@@ -225,8 +230,6 @@ bool equal(const A& a0, const B& b0)
|
||||
{
|
||||
const value_t ta = a.type();
|
||||
const value_t tb = b.type();
|
||||
const bool numbers = (ta == value_t::number_integer || ta == value_t::number_unsigned || ta == value_t::number_float)
|
||||
&& (tb == value_t::number_integer || tb == value_t::number_unsigned || tb == value_t::number_float);
|
||||
if (ta == value_t::discarded || tb == value_t::discarded)
|
||||
{
|
||||
// basic_json decides (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)
|
||||
@@ -237,13 +240,13 @@ bool equal(const A& a0, const B& b0)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!numbers && ta != tb)
|
||||
if (ta != tb && !(is_number(ta) && is_number(tb))) // numbers of different types can be equal
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ta == value_t::string)
|
||||
{
|
||||
if (!(a.string() == b.string()))
|
||||
if (a.string() != b.string())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -272,7 +275,7 @@ bool equal(const A& a0, const B& b0)
|
||||
}
|
||||
stack.push_back(std::move(f));
|
||||
}
|
||||
else if (!(a.scalar() == b.scalar())) // numbers (also of different types), null, boolean
|
||||
else if (a.scalar() != b.scalar()) // numbers (also of different types), null, boolean
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -289,7 +292,7 @@ bool equal(const A& a0, const B& b0)
|
||||
const std::size_t count = f.object ? f.members_a.size() : f.elements_a.size();
|
||||
if (f.next == count)
|
||||
{
|
||||
stack.pop_back();
|
||||
stack.pop_back(); // (f dangles now, and is bound again after continue)
|
||||
continue;
|
||||
}
|
||||
if (f.object)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
@@ -46,7 +46,7 @@ struct document_data
|
||||
std::string owned; ///< owned copy of the input, if any (a std::string, so that its NUL serves as the parser's sentinel)
|
||||
std::vector<std::uint8_t> owned_image; ///< a loaded image the document owns (the text and the decoded strings point into it)
|
||||
|
||||
// hash indexes of large objects (see object_index.hpp)
|
||||
// hash indexes of the keys of large objects (see object_index.hpp)
|
||||
static constexpr std::uint32_t index_min_members = 128;
|
||||
struct object_index
|
||||
{
|
||||
@@ -56,14 +56,16 @@ struct document_data
|
||||
std::vector<object_index> indexes;
|
||||
std::vector<std::uint32_t> index_slots;
|
||||
std::vector<std::uint32_t> large_objects; ///< positions of the objects to index (noted while parsing)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena (indexed by flags & node_flags::storage)
|
||||
std::array<const char*, 4> base = {{nullptr, nullptr, nullptr, nullptr}}; ///< string bases: source, arena, edit arena, unused (indexed by flags & node_flags::storage)
|
||||
bool discarded = true;
|
||||
|
||||
/// The storage of edits (editable documents only; see edit_storage.hpp).
|
||||
/// Edits never move or resize the parsed index, so views stay valid: an
|
||||
/// array/object whose elements change gets node_flags::moved, and its
|
||||
/// elements then live in a separate sequence (a header node, then the
|
||||
/// entries), whose entries link to the values.
|
||||
/// No edit moves or resizes the parsed index, and the nodes of values
|
||||
/// written by edits never move either, so a view stays valid whenever it
|
||||
/// was taken, before the first edit or between later ones: an array/object
|
||||
/// whose elements change gets node_flags::moved, and its elements then live
|
||||
/// in a separate sequence (a header node, then the entries), whose entries
|
||||
/// link to the values.
|
||||
struct edit_state
|
||||
{
|
||||
std::vector<node*> moved{}; ///< element sequences of moved arrays/objects (header node first) // NOLINT(readability-redundant-member-init)
|
||||
@@ -183,6 +185,12 @@ struct document_data
|
||||
return base[n.flags & node_flags::storage] + n.off;
|
||||
}
|
||||
|
||||
/// whether the string of node k (a key) is the n bytes at key
|
||||
bool key_equals(const node& k, const char* key, std::size_t n) const noexcept
|
||||
{
|
||||
return k.len == n && (n == 0 || std::memcmp(str(k), key, n) == 0);
|
||||
}
|
||||
|
||||
/// the node after n's subtree (containers span `next` nodes, scalars one)
|
||||
static NLOHMANN_VIEW_ALWAYS_INLINE const node* after(const node* n) noexcept
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <cmath> // isinf, isnan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memmove
|
||||
#include <cstring> // memmove
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string, to_string
|
||||
#include <type_traits> // decay, enable_if, integral_constant, is_arithmetic, is_convertible, is_floating_point, is_same, is_signed
|
||||
@@ -313,7 +313,7 @@ class editor
|
||||
|
||||
bool key_equals(const node& k, string_view_t key) const noexcept
|
||||
{
|
||||
return k.len == key.size() && (key.size() == 0 || std::memcmp(m_doc.str(k), key.data(), key.size()) == 0);
|
||||
return m_doc.key_equals(k, key.data(), key.size());
|
||||
}
|
||||
|
||||
/// remove the members with this key (all, or all but the first) from an object
|
||||
|
||||
@@ -50,7 +50,19 @@ inline node* alloc_nodes(document_data& d, std::size_t k)
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(e.chunk_end - e.chunk_cur) < k))
|
||||
{
|
||||
const std::size_t count = (std::max)(k, e.chunk_next);
|
||||
if (k >= e.chunk_next / 2)
|
||||
{
|
||||
// a large request (half of the next chunk or more) gets a chunk of
|
||||
// its own, so that the rest of the current chunk stays available
|
||||
// for smaller ones
|
||||
std::unique_ptr<node[]> own(new node[k]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(own));
|
||||
e.bytes += k * sizeof(node);
|
||||
return e.chunks.back().get();
|
||||
}
|
||||
// a smaller request leaves the rest of the current chunk unused
|
||||
// (fewer than k nodes, and k is less than half of the new chunk)
|
||||
const std::size_t count = e.chunk_next;
|
||||
std::unique_ptr<node[]> fresh(new node[count]()); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
e.chunks.push_back(std::move(fresh));
|
||||
e.chunk_cur = e.chunks.back().get();
|
||||
@@ -81,7 +93,8 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
||||
}
|
||||
|
||||
/// copy n bytes into the edit arena and return their offset; a new buffer
|
||||
/// leaves the old one alive, so that string views into it remain valid
|
||||
/// leaves the old one alive, so that string views into it remain valid; n is
|
||||
/// 0 for an empty string (written by an edit, or the key of a new member)
|
||||
inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
document_data::edit_state& e = edit_state_of(d);
|
||||
@@ -89,7 +102,7 @@ inline std::uint32_t append_text(document_data& d, const char* s, std::size_t n)
|
||||
{
|
||||
const std::size_t cap = text_capacity(e.text_cap, e.text_used, n);
|
||||
std::unique_ptr<char[]> fresh(new char[cap]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
if (e.text_used != 0)
|
||||
if (NLOHMANN_VIEW_LIKELY(e.text_used != 0)) // (0 only for the first buffer, and then texts is empty)
|
||||
{
|
||||
std::memcpy(fresh.get(), e.texts.back().get(), e.text_used);
|
||||
}
|
||||
@@ -127,8 +140,10 @@ inline void reserve_moved(document_data& d)
|
||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||
{
|
||||
// both grow before either changes, so that the push_backs cannot throw
|
||||
e.moved.reserve((2 * e.moved.size()) + 16);
|
||||
e.moved_cap.reserve((2 * e.moved.size()) + 16);
|
||||
// (both conditions are needed: reserve() may allocate more than asked,
|
||||
// so the two capacities can differ)
|
||||
e.moved.reserve((e.moved.size() * 2) + 16);
|
||||
e.moved_cap.reserve((e.moved.size() * 2) + 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // sort
|
||||
#include <algorithm> // min, sort
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstddef> // offsetof, size_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
||||
#include <cstring> // memcmp, memcpy
|
||||
#include <cstring> // memcmp, memcpy, memset
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <vector> // vector
|
||||
@@ -105,7 +105,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
}
|
||||
return r;
|
||||
};
|
||||
const auto emit = [&](const node * v)
|
||||
const auto copy_node = [&](const node * v)
|
||||
{
|
||||
node r = *v;
|
||||
switch (static_cast<value_t>(v->kind))
|
||||
@@ -159,10 +159,10 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
}
|
||||
out.push_back(r);
|
||||
};
|
||||
emit(d.nodes);
|
||||
copy_node(d.nodes);
|
||||
while (!stack.empty())
|
||||
{
|
||||
frame& top = stack.back();
|
||||
const frame top = stack.back();
|
||||
if (top.cur == top.end)
|
||||
{
|
||||
node& c = out[top.index];
|
||||
@@ -171,20 +171,14 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
stack.pop_back();
|
||||
continue;
|
||||
}
|
||||
++top.count;
|
||||
const node* v = nullptr;
|
||||
if (top.object)
|
||||
{
|
||||
out.push_back(string_node(*top.cur));
|
||||
v = document_data::deref(top.cur + 1);
|
||||
top.cur = document_data::after(top.cur + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = document_data::deref(top.cur);
|
||||
top.cur = document_data::after(top.cur);
|
||||
}
|
||||
emit(v); // may grow the stack (top is not used afterwards)
|
||||
const node* const value = top.cur + (top.object ? 1 : 0);
|
||||
stack.back().cur = document_data::after(value);
|
||||
++stack.back().count;
|
||||
copy_node(document_data::deref(value)); // may grow the stack
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,17 +189,15 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||
#endif
|
||||
const std::size_t arena_size = d.arena_size;
|
||||
const node* nodes = d.nodes;
|
||||
std::size_t count = d.node_count;
|
||||
std::vector<node> compacted;
|
||||
std::string text_tail;
|
||||
std::string arena_tail;
|
||||
if (d.edits)
|
||||
{
|
||||
compact_nodes(d, arena_size, compacted, text_tail, arena_tail);
|
||||
nodes = compacted.data();
|
||||
count = compacted.size();
|
||||
}
|
||||
const node* const nodes = d.edits ? compacted.data() : d.nodes;
|
||||
const std::size_t count = d.edits ? compacted.size() : d.node_count;
|
||||
const std::size_t text_size = d.size + text_tail.size();
|
||||
const std::size_t total_arena = arena_size + arena_tail.size();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||
@@ -225,14 +217,13 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||
std::memcpy(o, &h, sizeof(h));
|
||||
o += sizeof(h);
|
||||
std::memcpy(o, nodes, count * sizeof(node));
|
||||
// the hash indexes are rebuilt by load()
|
||||
// the hash indexes are rebuilt by load(), whose check rejects any other
|
||||
// extra of an object, and the same document saves to the same bytes
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
if (nodes[i].kind == static_cast<std::uint8_t>(value_t::object) && nodes[i].extra != 0)
|
||||
{
|
||||
node n = nodes[i];
|
||||
n.extra = 0;
|
||||
std::memcpy(o + (i * sizeof(node)), &n, sizeof(node));
|
||||
std::memset(o + (i * sizeof(node)) + offsetof(node, extra), 0, sizeof(nodes[i].extra));
|
||||
}
|
||||
}
|
||||
o += count * sizeof(node);
|
||||
@@ -271,21 +262,22 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
const unsigned char* p = s;
|
||||
t.negative = p != e && *p == '-';
|
||||
p += t.negative ? 1 : 0;
|
||||
t.int_start = p;
|
||||
if (p == e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
t.int_start = p;
|
||||
if (*p == '0')
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
while (p != e && is_digit(*p))
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -318,11 +310,12 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
do
|
||||
{
|
||||
t.exponent = t.exponent < 100000 ? (t.exponent * 10) + (*p - '0') : t.exponent;
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
t.exponent = exp_negative ? -t.exponent : t.exponent;
|
||||
t.is_float = true;
|
||||
}
|
||||
@@ -333,7 +326,7 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
/// writes "many" instead; the caller accepts both)
|
||||
inline std::uint16_t float_layout(const number_token& t)
|
||||
{
|
||||
return static_cast<std::uint16_t>((t.int_digits < 255 ? t.int_digits : 255) | ((t.frac_digits < 255 ? t.frac_digits : 255) << 8u));
|
||||
return static_cast<std::uint16_t>((std::min)(t.int_digits, std::size_t{255}) | ((std::min)(t.frac_digits, std::size_t{255}) << 8u));
|
||||
}
|
||||
|
||||
/// whether a float token (well-formed, per scan_number_token) is finite as
|
||||
@@ -735,14 +728,10 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
||||
// the hash indexes of large objects, as after parsing
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
node& n = d.nodes[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object))
|
||||
const node& n = d.nodes[i];
|
||||
if (n.kind == static_cast<std::uint8_t>(value_t::object) && n.len >= document_data::index_min_members)
|
||||
{
|
||||
n.extra = 0;
|
||||
if (n.len >= document_data::index_min_members)
|
||||
{
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
}
|
||||
d.large_objects.push_back(static_cast<std::uint32_t>(i));
|
||||
}
|
||||
}
|
||||
build_object_indexes(d);
|
||||
|
||||
@@ -112,7 +112,7 @@ const node* find_member(const document_data& d, const node* object, const char*
|
||||
}
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (m->len == n && std::memcmp(d.str(*m), key, n) == 0)
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
return m;
|
||||
}
|
||||
@@ -120,6 +120,23 @@ const node* find_member(const document_data& d, const node* object, const char*
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// the number of members of an object with this key (duplicates included)
|
||||
template<bool Editable>
|
||||
std::size_t count_members(const document_data& d, const node* object, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
std::size_t result = 0;
|
||||
const node* const end = nav::end(d, object);
|
||||
for (const node* m = nav::first(d, object); m != end; m = document_data::after(m + 1))
|
||||
{
|
||||
if (d.key_equals(*m, key, n))
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// whether an integer type is accepted as an array index by the view's
|
||||
/// operator[] and at(): every integer type but bool and size_t, which has its
|
||||
/// own overload
|
||||
|
||||
@@ -70,5 +70,11 @@
|
||||
#define NLOHMANN_VIEW_LITTLE_ENDIAN 0
|
||||
#endif
|
||||
|
||||
/// eight checks at fixed offsets 0..7
|
||||
#define NLOHMANN_VIEW_REPEAT_FIRST_8(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7)
|
||||
|
||||
/// eight checks at fixed offsets 8..15
|
||||
#define NLOHMANN_VIEW_REPEAT_SECOND_8(X) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15)
|
||||
|
||||
/// sixteen checks at fixed offsets 0..15
|
||||
#define NLOHMANN_VIEW_REPEAT16(X) X(0) X(1) X(2) X(3) X(4) X(5) X(6) X(7) X(8) X(9) X(10) X(11) X(12) X(13) X(14) X(15)
|
||||
#define NLOHMANN_VIEW_REPEAT16(X) NLOHMANN_VIEW_REPEAT_FIRST_8(X) NLOHMANN_VIEW_REPEAT_SECOND_8(X)
|
||||
@@ -19,6 +19,8 @@
|
||||
#undef NLOHMANN_VIEW_THROW
|
||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
#undef NLOHMANN_VIEW_REPEAT16
|
||||
#undef NLOHMANN_VIEW_REPEAT_SECOND_8
|
||||
#undef NLOHMANN_VIEW_REPEAT_FIRST_8
|
||||
#undef NLOHMANN_VIEW_NEON
|
||||
#undef NLOHMANN_VIEW_SSE2
|
||||
#undef NLOHMANN_VIEW_SSSE3
|
||||
|
||||
@@ -34,7 +34,7 @@ struct node_flags
|
||||
{
|
||||
static constexpr std::uint8_t escaped = 1; ///< string payload lives in the decode arena, not the source
|
||||
static constexpr std::uint8_t edited = 2; ///< string or number token lives in the edit arena (editable documents)
|
||||
static constexpr std::uint8_t storage = 3; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t storage = escaped | edited; ///< mask: where a string or number token lives (index into document_data::base)
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
@@ -53,7 +53,7 @@ struct node
|
||||
{
|
||||
std::uint8_t kind; ///< value_t, or kind_link
|
||||
std::uint8_t flags; ///< node_flags
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the hash index (1-based, 0 = none); otherwise 0
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
|
||||
@@ -45,10 +45,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
dot = std::string::npos;
|
||||
if (p != last && *p == '.')
|
||||
{
|
||||
@@ -57,10 +58,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
mantissa_end = static_cast<std::size_t>(p - first);
|
||||
if (p != last && (*p == 'e' || *p == 'E'))
|
||||
@@ -71,10 +73,11 @@ inline bool float_token_layout(const char* first, const char* last, std::size_t&
|
||||
{
|
||||
return false;
|
||||
}
|
||||
while (digit(p))
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (digit(p));
|
||||
}
|
||||
return p == last;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t, uint64_t
|
||||
#include <cstring> // memcmp
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
// Hash indexes of large objects, so that a lookup does not compare thousands
|
||||
// Hash indexes of the keys of large objects, so that a lookup does not compare thousands
|
||||
// of keys (as Boost.JSON switches from a linear search to a hash table for
|
||||
// large objects). An object with document_data::index_min_members members or
|
||||
// more gets an open-addressing table after parsing; its node stores the
|
||||
@@ -42,6 +41,12 @@ namespace view
|
||||
/// members; and every member costs at most this many steps while building)
|
||||
constexpr std::size_t index_max_displacement = 64;
|
||||
|
||||
// a table has at least 2 * len slots (len >= index_min_members), so the probe
|
||||
// window of index_max_displacement + 1 slots stays far smaller than the table
|
||||
// and a probe never wraps around onto its own start
|
||||
static_assert(document_data::index_min_members >= 2 * index_max_displacement,
|
||||
"a table must be much larger than the probe window of a lookup");
|
||||
|
||||
/// hash of a key: its bytes, eight at a time, in a fixed byte order
|
||||
inline std::uint64_t key_hash(const char* s, std::size_t n) noexcept
|
||||
{
|
||||
@@ -77,45 +82,36 @@ inline void build_object_index(document_data& d, node* obj)
|
||||
}
|
||||
const std::size_t start = d.index_slots.size();
|
||||
d.index_slots.resize(start + cap, 0);
|
||||
std::uint32_t* const slots = d.index_slots.data() + start;
|
||||
std::uint32_t* const table = d.index_slots.data() + start;
|
||||
const std::size_t mask = cap - 1;
|
||||
bool degenerate = false;
|
||||
for (const node* k = document_data::first_child(obj), *end = document_data::child_end(obj); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
const char* const key = d.str(*k);
|
||||
const std::uint64_t hash = key_hash(key, k->len); // (a cast of the call would be useless where std::uint64_t is std::size_t)
|
||||
std::size_t i = static_cast<std::size_t>(hash) & mask;
|
||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, k->len)) & mask;
|
||||
bool duplicate = false;
|
||||
std::size_t distance = 0;
|
||||
while (slots[i] != 0)
|
||||
while (table[slot] != 0)
|
||||
{
|
||||
const node* const other = obj + slots[i];
|
||||
if (other->len == k->len && (k->len == 0 || std::memcmp(d.str(*other), key, k->len) == 0))
|
||||
const node* const other = obj + table[slot];
|
||||
if (d.key_equals(*other, key, k->len))
|
||||
{
|
||||
duplicate = true; // keep the first
|
||||
break;
|
||||
}
|
||||
if (++distance > index_max_displacement)
|
||||
if (distance == index_max_displacement)
|
||||
{
|
||||
degenerate = true; // too many keys share a home region
|
||||
break;
|
||||
// too many keys share a home region
|
||||
d.index_slots.resize(start); // no table: the object is searched linearly
|
||||
return;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
if (degenerate)
|
||||
{
|
||||
break;
|
||||
++distance;
|
||||
slot = (slot + 1) & mask;
|
||||
}
|
||||
if (!duplicate)
|
||||
{
|
||||
slots[i] = static_cast<std::uint32_t>(k - obj);
|
||||
table[slot] = static_cast<std::uint32_t>(k - obj);
|
||||
}
|
||||
}
|
||||
if (degenerate)
|
||||
{
|
||||
d.index_slots.resize(start); // no table: the object is searched linearly
|
||||
return;
|
||||
}
|
||||
d.indexes.push_back(document_data::object_index{start, static_cast<std::uint32_t>(mask)});
|
||||
obj->extra = static_cast<std::uint16_t>(d.indexes.size());
|
||||
}
|
||||
@@ -134,22 +130,21 @@ inline void build_object_indexes(document_data& d)
|
||||
inline const node* find_indexed(const document_data& d, const node* obj, const char* key, std::size_t n) noexcept
|
||||
{
|
||||
const document_data::object_index& ix = d.indexes[obj->extra - 1u];
|
||||
const std::uint32_t* const slots = d.index_slots.data() + ix.start;
|
||||
const std::uint64_t hash = key_hash(key, n); // (a cast of the call would be useless where std::uint64_t is std::size_t)
|
||||
std::size_t i = static_cast<std::size_t>(hash) & ix.mask;
|
||||
const std::uint32_t* const table = d.index_slots.data() + ix.start;
|
||||
std::size_t slot = conditional_static_cast<std::size_t>(key_hash(key, n)) & ix.mask;
|
||||
for (std::size_t distance = 0; distance <= index_max_displacement; ++distance)
|
||||
{
|
||||
const std::uint32_t s = slots[i];
|
||||
const std::uint32_t s = table[slot];
|
||||
if (s == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
const node* const k = obj + s;
|
||||
if (k->len == n && (n == 0 || std::memcmp(d.str(*k), key, n) == 0))
|
||||
if (d.key_equals(*k, key, n))
|
||||
{
|
||||
return k;
|
||||
}
|
||||
i = (i + 1) & ix.mask;
|
||||
slot = (slot + 1) & ix.mask;
|
||||
}
|
||||
return nullptr; // (no key sits further from its home slot)
|
||||
}
|
||||
|
||||
@@ -47,24 +47,24 @@ enum class index_status
|
||||
template<typename StringType>
|
||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
{
|
||||
// as basic_json: the empty token and "-" (past the last element) are
|
||||
// unresolved rather than not a number
|
||||
if (s.empty() || (s.size() == 1 && s[0] == '-'))
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
if (s[0] < '0' || s[0] > '9')
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_status::leading_zero;
|
||||
}
|
||||
// as basic_json: also a single character, but "-" and the empty token are
|
||||
// left to the conversion below and reported as unresolved
|
||||
if (!s.empty() && !(s.size() == 1 && s[0] == '-') && !(s[0] >= '0' && s[0] <= '9'))
|
||||
{
|
||||
return index_status::not_number;
|
||||
}
|
||||
if (s.empty())
|
||||
{
|
||||
return index_status::unresolved;
|
||||
}
|
||||
std::uint64_t v = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
for (const auto c : s)
|
||||
{
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(s[i])) - '0';
|
||||
const auto d = static_cast<unsigned>(static_cast<unsigned char>(c)) - '0';
|
||||
if (d > 9 || v > ((std::numeric_limits<std::uint64_t>::max)() - d) / 10)
|
||||
{
|
||||
return index_status::unresolved; // not a number, or beyond unsigned long long
|
||||
|
||||
@@ -83,14 +83,12 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* scan_string_run(const unsigned
|
||||
if (e - p >= 16)
|
||||
{
|
||||
#define NLOHMANN_VIEW_STEP(i) if (NLOHMANN_VIEW_LIKELY(plain[p[i]] != 0)) {} else { p += (i); goto stop; }
|
||||
NLOHMANN_VIEW_STEP(0) NLOHMANN_VIEW_STEP(1) NLOHMANN_VIEW_STEP(2) NLOHMANN_VIEW_STEP(3)
|
||||
NLOHMANN_VIEW_STEP(4) NLOHMANN_VIEW_STEP(5) NLOHMANN_VIEW_STEP(6) NLOHMANN_VIEW_STEP(7)
|
||||
NLOHMANN_VIEW_REPEAT_FIRST_8(NLOHMANN_VIEW_STEP)
|
||||
#if NLOHMANN_VIEW_VECTOR
|
||||
if (!Value) // (a plain `!Value || !NLOHMANN_VIEW_VECTOR` is a constant condition for MSVC, C4127)
|
||||
#endif
|
||||
{
|
||||
NLOHMANN_VIEW_STEP(8) NLOHMANN_VIEW_STEP(9) NLOHMANN_VIEW_STEP(10) NLOHMANN_VIEW_STEP(11)
|
||||
NLOHMANN_VIEW_STEP(12) NLOHMANN_VIEW_STEP(13) NLOHMANN_VIEW_STEP(14) NLOHMANN_VIEW_STEP(15)
|
||||
NLOHMANN_VIEW_REPEAT_SECOND_8(NLOHMANN_VIEW_STEP)
|
||||
p += 8;
|
||||
}
|
||||
#undef NLOHMANN_VIEW_STEP
|
||||
@@ -177,32 +175,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
||||
/// powers of ten up to 10^19 as integers
|
||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||
{
|
||||
static const std::array<std::uint64_t, 20> table =
|
||||
{
|
||||
{
|
||||
1u,
|
||||
10u,
|
||||
100u,
|
||||
1000u,
|
||||
10000u,
|
||||
100000u,
|
||||
1000000u,
|
||||
10000000u,
|
||||
100000000u,
|
||||
1000000000u,
|
||||
10000000000u,
|
||||
100000000000u,
|
||||
1000000000000u,
|
||||
10000000000000u,
|
||||
100000000000000u,
|
||||
1000000000000000u,
|
||||
10000000000000000u,
|
||||
100000000000000000u,
|
||||
1000000000000000000u,
|
||||
10000000000000000000u
|
||||
}
|
||||
};
|
||||
return table[k];
|
||||
return ::nlohmann::detail::dtoa_impl::powers_of_ten()[k];
|
||||
}
|
||||
|
||||
/// value of 0 < k < 8 digits at p in one step if [p, p + 8) lies below
|
||||
|
||||
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user