Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e13f834c1 | ||
|
|
e13e976801 | ||
|
|
898e6ee81d | ||
|
|
ef1cdb59ac | ||
|
|
bd5227c2ec | ||
|
|
0cbe95ffa2 | ||
|
|
08d7fbb0a1 | ||
|
|
4155e2938f | ||
|
|
4971200b54 | ||
|
|
823eee3b59 | ||
|
|
3ff37ead75 | ||
|
|
4c3a3eb5b5 | ||
|
|
43e97b629b | ||
|
|
122d718330 | ||
|
|
a4974e72c1 | ||
|
|
a55cae45fd | ||
|
|
667e296707 | ||
|
|
ec2e50e26f | ||
|
|
4da932f518 | ||
|
|
a2248ed5d5 | ||
|
|
cc8334113c |
No files matched your search
@@ -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"
|
||||
]
|
||||
}
|
||||
+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"
|
||||
|
||||
|
||||
@@ -116,6 +116,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 +141,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
|
||||
|
||||
@@ -148,6 +156,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -370,7 +370,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -166,6 +166,43 @@ constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter keeps the current token itself and can
|
||||
// hand it out on demand (see input_stream_adapter::supports_token_tracking),
|
||||
// so the lexer need not copy every scanned character into token_string.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_token_tracking = decltype(InputAdapterType::supports_token_tracking);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_token_tracking;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_token_tracking(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether the contiguous block of a bulk-scan adapter is only a window
|
||||
// into the input (see input_stream_adapter::bulk_window): its end is not the
|
||||
// end of the input, so a token that touches it must be finished character by
|
||||
// character.
|
||||
template<typename InputAdapterType>
|
||||
using detect_bulk_window = decltype(InputAdapterType::bulk_window);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::bulk_window;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief lexical analysis
|
||||
|
||||
@@ -184,9 +221,20 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// whether the last read token can be reconstructed from the input adapter
|
||||
/// on demand (in error paths) instead of being copied on every scanned
|
||||
/// character; see input_adapter_supports_seek
|
||||
static constexpr bool lazy_token_string =
|
||||
static constexpr bool seekable =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether the adapter keeps the token itself (mark_token_start() /
|
||||
/// copy_token()); see input_adapter_supports_token_tracking
|
||||
static constexpr bool adapter_token =
|
||||
!seekable && input_adapter_supports_token_tracking<InputAdapterType>(is_detected<detect_supports_token_tracking, InputAdapterType> {});
|
||||
|
||||
/// whether the last read token is obtained from the input adapter on
|
||||
/// demand (in error paths) instead of being copied on every scanned
|
||||
/// character: seekable adapters reconstruct it from the consumed input,
|
||||
/// token-tracking adapters keep it themselves
|
||||
static constexpr bool lazy_token_string = seekable || adapter_token;
|
||||
|
||||
/// whether the read position is derived from the input adapter on demand
|
||||
/// instead of being counted on every scanned character. Seekable adapters
|
||||
/// know how much input they consumed and can revisit it, so
|
||||
@@ -194,7 +242,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// and line and column are recomputed from the consumed input only when
|
||||
/// get_position() is called (errors). Counting per character kept two
|
||||
/// increments and a newline check in the hot path of every get().
|
||||
static constexpr bool lazy_position = lazy_token_string;
|
||||
static constexpr bool lazy_position = seekable;
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
@@ -203,14 +251,17 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
/// the per-character capture in get() cannot lose error diagnostics. It
|
||||
/// also implies lazy_position, so the bulk paths need not advance the
|
||||
/// position counters.
|
||||
/// directly from a contiguous input buffer (SWAR fast path). Without
|
||||
/// lazy_token_string / lazy_position, the bulk paths copy the consumed
|
||||
/// characters into token_string and advance the position counters
|
||||
/// themselves (see bulk_consume).
|
||||
static constexpr bool bulk_scan =
|
||||
lazy_token_string
|
||||
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||
|
||||
/// whether the contiguous block is only a window into the input, so that
|
||||
/// its end does not end a token; see input_adapter_bulk_window
|
||||
static constexpr bool bulk_window =
|
||||
input_adapter_bulk_window<InputAdapterType>(is_detected<detect_bulk_window, InputAdapterType> {});
|
||||
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
@@ -219,7 +270,11 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
: ia(std::move(adapter))
|
||||
, ignore_comments(ignore_comments_)
|
||||
, discard_number_values(discard_number_values_)
|
||||
{}
|
||||
{
|
||||
// the token string of an error before the first token is everything
|
||||
// read so far
|
||||
begin_token(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
// deleted because of pointer members
|
||||
lexer(const lexer&) = delete;
|
||||
@@ -236,8 +291,8 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// contiguous input: try to decode the 4 hex digits following `\\u`
|
||||
/// directly from the input buffer via hex_codepoint(), instead of 4 calls
|
||||
/// to get(). On success, advances the adapter exactly as those 4 get()
|
||||
/// calls would (bulk_scan implies lazy_position, so there are no position
|
||||
/// counters to advance) and leaves @a current holding the last of
|
||||
/// calls would (including the position and token bookkeeping of a
|
||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
@@ -256,7 +311,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
return false;
|
||||
}
|
||||
ia.bulk_skip(4);
|
||||
bulk_consume(reinterpret_cast<const char*>(raw), 4);
|
||||
current = char_traits<char_type>::to_int_type(raw[3]);
|
||||
out = codepoint;
|
||||
return true;
|
||||
@@ -385,7 +440,36 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
return;
|
||||
}
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
ia.bulk_skip(pos);
|
||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||
}
|
||||
|
||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||
/// the bookkeeping get() would have done for them; the characters are
|
||||
/// never newlines
|
||||
void bulk_consume(const char* data, std::size_t n)
|
||||
{
|
||||
bulk_track_position(n, std::integral_constant<bool, lazy_position> {});
|
||||
bulk_track_token(data, n, std::integral_constant<bool, lazy_token_string> {});
|
||||
ia.bulk_skip(n);
|
||||
}
|
||||
|
||||
/// seekable adapter: the position is derived from the adapter
|
||||
void bulk_track_position(std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: count the characters
|
||||
void bulk_track_position(std::size_t n, std::false_type /*lazy*/) noexcept
|
||||
{
|
||||
position.chars_read_total += n;
|
||||
position.chars_read_current_line += n;
|
||||
}
|
||||
|
||||
/// the token is obtained from the adapter
|
||||
void bulk_track_token(const char* /*data*/, std::size_t /*n*/, std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
/// streaming adapter: capture the characters
|
||||
void bulk_track_token(const char* data, std::size_t n, std::false_type /*lazy*/)
|
||||
{
|
||||
token_string.insert(token_string.end(), data, data + n);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -1541,9 +1625,12 @@ scan_number_done:
|
||||
const std::size_t avail = rem + 1;
|
||||
|
||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||
// and accumulate the mantissa digits on the way, so that tokens with up
|
||||
// to 19 digits need no second pass for their value
|
||||
std::size_t i = 0;
|
||||
std::size_t dot_index = std::string::npos;
|
||||
token_type number_type = token_type::value_unsigned;
|
||||
std::uint64_t w = 0;
|
||||
if (data[0] == '-')
|
||||
{
|
||||
number_type = token_type::value_integer;
|
||||
@@ -1553,22 +1640,21 @@ scan_number_done:
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
}
|
||||
const std::size_t integer_begin = i;
|
||||
if (data[i] == '0')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else if (data[i] >= '1' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
i = accumulate_digits(data, i, avail, w);
|
||||
}
|
||||
else
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
std::size_t digit_count = i - integer_begin;
|
||||
std::size_t fraction_digits = 0;
|
||||
if (i < avail && data[i] == '.')
|
||||
{
|
||||
number_type = token_type::value_float;
|
||||
@@ -1578,31 +1664,45 @@ scan_number_done:
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
const std::size_t fraction_begin = i;
|
||||
i = accumulate_digits(data, i, avail, w);
|
||||
fraction_digits = i - fraction_begin;
|
||||
digit_count += fraction_digits;
|
||||
}
|
||||
// the mantissa ends here, whether or not an exponent part follows
|
||||
const std::size_t mantissa_end = i;
|
||||
std::int64_t exponent = 0;
|
||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||
{
|
||||
number_type = token_type::value_float;
|
||||
++i;
|
||||
bool negative_exponent = false;
|
||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||
{
|
||||
negative_exponent = data[i] == '-';
|
||||
++i;
|
||||
}
|
||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
// saturated far beyond every range, see parse_float_exponent()
|
||||
constexpr std::int64_t saturation = 100000000000000000; // 10^17
|
||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||
{
|
||||
if (exponent < saturation)
|
||||
{
|
||||
exponent = (exponent * 10) + (data[i] - '0');
|
||||
}
|
||||
++i;
|
||||
}
|
||||
exponent = negative_exponent ? -exponent : exponent;
|
||||
}
|
||||
const std::size_t len = i;
|
||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
|
||||
// reset() records where this token starts (for diagnostics), so it has
|
||||
// to run before the input position advances below
|
||||
@@ -1617,10 +1717,14 @@ scan_number_done:
|
||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||
if (number_type != token_type::value_float)
|
||||
{
|
||||
const token_type integer_result = convert_integer(number_type, data, data + len);
|
||||
// up to 18 digits always fit into std::uint64_t and std::int64_t
|
||||
const token_type integer_result = (digit_count <= 18)
|
||||
? store_integer(number_type, w)
|
||||
: convert_integer(number_type, data, data + len);
|
||||
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||
{
|
||||
ia.bulk_skip(len - 1);
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
return integer_result;
|
||||
}
|
||||
// The value does not fit an integer, so this token converts as a
|
||||
@@ -1630,17 +1734,99 @@ scan_number_done:
|
||||
}
|
||||
#endif
|
||||
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
bulk_consume(data + 1, len - 1);
|
||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
||||
|
||||
// a float whose digits all fit into w converts from the accumulated
|
||||
// value; longer ones (and other float formats) take the general path
|
||||
if (number_type == token_type::value_float && digit_count <= 19)
|
||||
{
|
||||
float_significand s;
|
||||
s.w = w;
|
||||
s.exponent = exponent - static_cast<std::int64_t>(fraction_digits);
|
||||
s.negative = data[0] == '-';
|
||||
if (convert_accumulated_float(s, std::integral_constant<bool, has_native_float_format<number_float_t>::value> {}))
|
||||
{
|
||||
// the token text is only copied into token_buffer when
|
||||
// get_string() asks for it; the library's own SAX handlers
|
||||
// ignore it. A window into a stream may be refilled by the
|
||||
// next read, so there the text is copied right away.
|
||||
if (bulk_window)
|
||||
{
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||
}
|
||||
else
|
||||
{
|
||||
pending_number_text = data;
|
||||
pending_number_length = len;
|
||||
}
|
||||
return token_type::value_float;
|
||||
}
|
||||
}
|
||||
|
||||
// materialize the token exactly as scan_number() would. reset() already
|
||||
// cleared token_buffer, so append() fills it (assign() is avoided
|
||||
// because custom string_t types need not provide it)
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||
decimal_point_position = dot_index;
|
||||
|
||||
ia.bulk_skip(len - 1);
|
||||
|
||||
return convert_number(number_type, mantissa_end);
|
||||
}
|
||||
|
||||
/// window adapter: a number that reaches the end of the window may continue
|
||||
/// after it, so scan_number() has to read it character by character
|
||||
static constexpr bool number_may_continue(bool at_end, std::true_type /*window*/) noexcept
|
||||
{
|
||||
return at_end;
|
||||
}
|
||||
|
||||
/// the end of the block is the end of the input, which ends the number
|
||||
static constexpr bool number_may_continue(bool /*at_end*/, std::false_type /*window*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// window adapter: read the character after a bulk-scanned number and unget
|
||||
/// it, as scan_number() does. The adapter is a stream that is handed back to
|
||||
/// the user, who must find the same input consumed either way.
|
||||
void read_number_terminator(std::true_type /*window*/)
|
||||
{
|
||||
get();
|
||||
unget();
|
||||
}
|
||||
|
||||
/// the input is not handed back, so the terminator can stay unread
|
||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
||||
|
||||
/// binary32/binary64: the value of a float token from its accumulated
|
||||
/// significand (see scan_number_bulk_contiguous())
|
||||
bool convert_accumulated_float(const float_significand& s, std::true_type /*native*/) noexcept
|
||||
{
|
||||
value_float = decimal_to_float<number_float_t>(s);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// other formats (e.g. x87 long double): not instantiated for them, as
|
||||
/// decimal_to_float() only supports binary32/binary64
|
||||
static bool convert_accumulated_float(const float_significand& /*s*/, std::false_type /*native*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// the value of an integer token with at most 18 digits, accumulated in @a w
|
||||
token_type store_integer(token_type number_type, std::uint64_t w) noexcept
|
||||
{
|
||||
if (number_type == token_type::value_unsigned)
|
||||
{
|
||||
value_unsigned = static_cast<number_unsigned_t>(w);
|
||||
// reject values that do not round-trip into a narrower type
|
||||
return (static_cast<std::uint64_t>(value_unsigned) == w) ? token_type::value_unsigned : token_type::uninitialized;
|
||||
}
|
||||
const std::int64_t x = -static_cast<std::int64_t>(w);
|
||||
value_integer = static_cast<number_integer_t>(x);
|
||||
return (static_cast<std::int64_t>(value_integer) == x) ? token_type::value_integer : token_type::uninitialized;
|
||||
}
|
||||
|
||||
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -1683,6 +1869,7 @@ scan_number_done:
|
||||
void reset() noexcept
|
||||
{
|
||||
token_buffer.clear();
|
||||
pending_number_text = nullptr;
|
||||
decimal_point_position = std::string::npos;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
@@ -1696,11 +1883,30 @@ scan_number_done:
|
||||
/// seekable adapter: remember where the current token starts so it can be
|
||||
/// reconstructed from the input on error; current has already been
|
||||
/// consumed, hence the -1
|
||||
void note_token_start(std::true_type /*lazy*/) noexcept
|
||||
void note_token_start(std::true_type /*lazy*/)
|
||||
{
|
||||
note_token_start_lazy(std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
void note_token_start_lazy(std::false_type /*adapter_token*/) noexcept
|
||||
{
|
||||
token_string_start = ia.get_consumed_count() - 1;
|
||||
}
|
||||
|
||||
/// token-tracking adapter: start tracking the token at the first character
|
||||
void begin_token(std::true_type /*adapter_token*/) noexcept
|
||||
{
|
||||
ia.begin_token();
|
||||
}
|
||||
|
||||
void begin_token(std::false_type /*adapter_token*/) const noexcept {}
|
||||
|
||||
/// token-tracking adapter: the token starts with the current character
|
||||
void note_token_start_lazy(std::true_type /*adapter_token*/)
|
||||
{
|
||||
ia.mark_token_start(current);
|
||||
}
|
||||
|
||||
/// streaming adapter: start copying the token eagerly, beginning with the
|
||||
/// already-read first character
|
||||
void note_token_start(std::false_type /*lazy*/) noexcept
|
||||
@@ -1923,10 +2129,30 @@ scan_number_done:
|
||||
/// return current string value
|
||||
string_t& get_string()
|
||||
{
|
||||
if (pending_number_text != nullptr)
|
||||
{
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(pending_number_text), pending_number_length);
|
||||
pending_number_text = nullptr;
|
||||
}
|
||||
// a number token holds '.' regardless of the locale (#4084)
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/// the value of a string token (key or string), which never has pending
|
||||
/// number text, so get_string()'s check is not needed
|
||||
string_t& get_string_token() noexcept
|
||||
{
|
||||
JSON_ASSERT(pending_number_text == nullptr);
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/// the text of a float token for a SAX handler that ignores it (the
|
||||
/// library's own handlers): empty, without copying the token
|
||||
const string_t& get_ignored_number_text() const noexcept
|
||||
{
|
||||
return token_buffer;
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
// diagnostics
|
||||
/////////////////////
|
||||
@@ -2021,6 +2247,20 @@ scan_number_done:
|
||||
public:
|
||||
/// seekable adapter: rebuild the last read token from the input on demand
|
||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||
{
|
||||
return collect_token_chars_lazy(out, std::integral_constant<bool, adapter_token> {});
|
||||
}
|
||||
|
||||
/// token-tracking adapter: the adapter hands out everything read since
|
||||
/// mark_token_start(), except a pending unget (see below)
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::true_type /*adapter_token*/) const
|
||||
{
|
||||
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
|
||||
ia.copy_token(out, pending_real_unget);
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<char_type>& collect_token_chars_lazy(std::vector<char_type>& out, std::false_type /*adapter_token*/) const
|
||||
{
|
||||
// a pending unget of a real (non-EOF) character means that character
|
||||
// was consumed from the input but is not part of the token; EOF is
|
||||
@@ -2142,9 +2382,8 @@ scan_number_done:
|
||||
Pretty-printed JSON indents every line with a run of spaces, which the loop
|
||||
in skip_whitespace() would otherwise read one character at a time. The
|
||||
spaces are skipped 8 bytes at a time; the last 7 bytes of the input and any
|
||||
other whitespace are left to that loop. Skipping bypasses get(), which is
|
||||
fine because bulk_scan implies lazy_position: the position is derived from
|
||||
the adapter, not counted per character.
|
||||
other whitespace are left to that loop. Skipping bypasses get();
|
||||
bulk_consume() does its bookkeeping.
|
||||
*/
|
||||
void skip_indentation(std::true_type /*bulk*/)
|
||||
{
|
||||
@@ -2167,7 +2406,7 @@ scan_number_done:
|
||||
break;
|
||||
}
|
||||
}
|
||||
ia.bulk_skip(pos);
|
||||
bulk_consume(data, pos);
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
@@ -2309,6 +2548,11 @@ scan_number_done:
|
||||
/// whether the next get() call should just return current
|
||||
bool next_unget = false;
|
||||
|
||||
/// the text of the current float token in the input, not yet copied into
|
||||
/// token_buffer (see get_string()), or nullptr
|
||||
const char* pending_number_text = nullptr;
|
||||
std::size_t pending_number_length = 0;
|
||||
|
||||
/// the start position of the current token
|
||||
position_t position {};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -171,6 +185,23 @@ class parser
|
||||
}
|
||||
|
||||
private:
|
||||
/// 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();
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief run a DOM SAX parser to completion and position the lexer
|
||||
|
||||
@@ -247,7 +278,7 @@ class parser
|
||||
{
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -301,7 +332,7 @@ class parser
|
||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
||||
}
|
||||
|
||||
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 +378,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;
|
||||
}
|
||||
@@ -456,7 +487,7 @@ class parser
|
||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))
|
||||
if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string_token())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#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 +48,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -73,6 +73,13 @@ class output_buffer
|
||||
m_pos += n;
|
||||
}
|
||||
|
||||
/// a string literal, without its terminating NUL
|
||||
template<std::size_t N>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void put(const char (&s)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
put(s, N - 1);
|
||||
}
|
||||
|
||||
void put_repeated(char c, std::size_t n)
|
||||
{
|
||||
reserve(n);
|
||||
@@ -102,7 +109,7 @@ class output_buffer
|
||||
/// which a static constexpr member does not have before C++17)
|
||||
static constexpr std::size_t step() noexcept
|
||||
{
|
||||
return 65536;
|
||||
return 0x10000;
|
||||
}
|
||||
|
||||
static StringType& sized(StringType& out, std::size_t estimate)
|
||||
@@ -167,14 +174,17 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||
/// shallow documents need no allocation. The top is a pointer, as in
|
||||
/// std::vector. The address of the stack never escapes (the growth gets the
|
||||
/// vector and returns the new storage), so its pointers stay in registers.
|
||||
/// std::vector. grow() is static and out of line: it gets the vector and the
|
||||
/// used range and returns the new storage, so the address of the stack never
|
||||
/// reaches a call. Otherwise the compiler would have to keep the three
|
||||
/// pointers in memory and load and store them on every push_back() and
|
||||
/// pop_back(), not just when growing.
|
||||
template<typename T>
|
||||
class small_stack
|
||||
{
|
||||
public:
|
||||
small_stack(T* buffer, std::size_t capacity, std::vector<T>& heap) noexcept
|
||||
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(&heap)
|
||||
: m_begin(buffer), m_top(buffer), m_end(buffer + capacity), m_heap(heap)
|
||||
{}
|
||||
small_stack(const small_stack&) = delete;
|
||||
small_stack(small_stack&&) = delete;
|
||||
@@ -188,7 +198,7 @@ class small_stack
|
||||
{
|
||||
const std::size_t used = size();
|
||||
const std::size_t capacity = 2 * static_cast<std::size_t>(m_end - m_begin);
|
||||
m_begin = grow(*m_heap, m_begin, used, capacity);
|
||||
m_begin = grow(m_heap, m_begin, used, capacity);
|
||||
m_top = m_begin + used;
|
||||
m_end = m_begin + capacity;
|
||||
}
|
||||
@@ -228,9 +238,18 @@ class small_stack
|
||||
T* m_begin;
|
||||
T* m_top;
|
||||
T* m_end;
|
||||
std::vector<T>* m_heap;
|
||||
std::vector<T>& m_heap;
|
||||
};
|
||||
|
||||
/// a string literal at w, without its terminating NUL (after room())
|
||||
/// @return a pointer past it
|
||||
template<std::size_t N>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE char* put_literal(char* w, const char (&s)[N]) noexcept // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
std::memcpy(w, s, N - 1);
|
||||
return w + N - 1;
|
||||
}
|
||||
|
||||
/// how the view's dump() writes a value
|
||||
struct dump_style
|
||||
{
|
||||
@@ -319,15 +338,15 @@ class view_serializer
|
||||
m_out.finish();
|
||||
return;
|
||||
}
|
||||
frame& f = stack.back();
|
||||
if (f.pos == f.end)
|
||||
if (stack.back().pos == stack.back().end)
|
||||
{
|
||||
const bool object = f.object;
|
||||
const bool object = stack.back().object;
|
||||
stack.pop_back();
|
||||
newline(stack.size());
|
||||
m_out.put(object ? '}' : ']');
|
||||
continue;
|
||||
}
|
||||
frame& f = stack.back();
|
||||
if (!f.first)
|
||||
{
|
||||
m_out.put(',');
|
||||
@@ -339,7 +358,7 @@ class view_serializer
|
||||
write_string(*f.pos);
|
||||
if (m_style.pretty)
|
||||
{
|
||||
m_out.put(": ", 2);
|
||||
m_out.put(": ");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -454,12 +473,6 @@ class view_serializer
|
||||
}
|
||||
w += n;
|
||||
};
|
||||
// a literal of n bytes (after room(n))
|
||||
const auto literal = [&](const char* text, std::size_t n)
|
||||
{
|
||||
std::memcpy(w, text, n);
|
||||
w += n;
|
||||
};
|
||||
// a string that is not a plain string of the source (out of line, so
|
||||
// that the cursor stays in a register here)
|
||||
const auto escaped = [&](const node & n)
|
||||
@@ -545,11 +558,11 @@ class view_serializer
|
||||
room(8);
|
||||
if ((n->flags & node_flags::is_true) != 0)
|
||||
{
|
||||
literal("true", 4);
|
||||
w = put_literal(w, "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
literal("false", 5);
|
||||
w = put_literal(w, "false");
|
||||
}
|
||||
break;
|
||||
case value_t::object:
|
||||
@@ -559,7 +572,7 @@ class view_serializer
|
||||
room(8);
|
||||
if (n->len == 0)
|
||||
{
|
||||
literal(object ? "{}" : "[]", 2);
|
||||
w = object ? put_literal(w, "{}") : put_literal(w, "[]");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -579,7 +592,7 @@ class view_serializer
|
||||
}
|
||||
case value_t::null:
|
||||
room(8);
|
||||
literal("null", 4);
|
||||
w = put_literal(w, "null");
|
||||
break;
|
||||
case value_t::binary: // LCOV_EXCL_LINE (not in a document)
|
||||
case value_t::discarded: // LCOV_EXCL_LINE
|
||||
@@ -666,16 +679,16 @@ class view_serializer
|
||||
switch (static_cast<value_t>(n.kind))
|
||||
{
|
||||
case value_t::null:
|
||||
m_out.put("null", 4);
|
||||
m_out.put("null");
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & node_flags::is_true) != 0)
|
||||
{
|
||||
m_out.put("true", 4);
|
||||
m_out.put("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_out.put("false", 5);
|
||||
m_out.put("false");
|
||||
}
|
||||
break;
|
||||
case value_t::string:
|
||||
@@ -759,7 +772,7 @@ class view_serializer
|
||||
// check also keeps an image that was only checked for bounds,
|
||||
// whose token may not be made of digits, from the counted
|
||||
// overload)
|
||||
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten_16();
|
||||
const auto& powers = ::nlohmann::detail::dtoa_impl::powers_of_ten();
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
return count - 1u < 15u && d.w >= powers[count - 1u] && d.w < powers[count]
|
||||
? ::nlohmann::detail::dtoa_impl::write_short_decimal(w, d.w, static_cast<int>(count), static_cast<int>(d.exponent))
|
||||
@@ -802,12 +815,14 @@ class view_serializer
|
||||
{
|
||||
if (!std::isfinite(x))
|
||||
{
|
||||
m_out.put("null", 4);
|
||||
m_out.put("null");
|
||||
return;
|
||||
}
|
||||
write_float(x, std::integral_constant < bool,
|
||||
(std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
||||
|| (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024) > {});
|
||||
static constexpr bool is_ieee_single_or_double
|
||||
= std::numeric_limits<number_float_t>::is_iec559
|
||||
&& ((std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128)
|
||||
|| (std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024));
|
||||
write_float(x, std::integral_constant<bool, is_ieee_single_or_double>());
|
||||
}
|
||||
|
||||
void write_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)
|
||||
@@ -902,25 +917,25 @@ class view_serializer
|
||||
switch (codepoint)
|
||||
{
|
||||
case 0x08:
|
||||
m_out.put("\\b", 2);
|
||||
m_out.put("\\b");
|
||||
return;
|
||||
case 0x09:
|
||||
m_out.put("\\t", 2);
|
||||
m_out.put("\\t");
|
||||
return;
|
||||
case 0x0A:
|
||||
m_out.put("\\n", 2);
|
||||
m_out.put("\\n");
|
||||
return;
|
||||
case 0x0C:
|
||||
m_out.put("\\f", 2);
|
||||
m_out.put("\\f");
|
||||
return;
|
||||
case 0x0D:
|
||||
m_out.put("\\r", 2);
|
||||
m_out.put("\\r");
|
||||
return;
|
||||
case 0x22:
|
||||
m_out.put("\\\"", 2);
|
||||
m_out.put("\\\"");
|
||||
return;
|
||||
case 0x5C:
|
||||
m_out.put("\\\\", 2);
|
||||
m_out.put("\\\\");
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // nullptr_t, size_t
|
||||
#include <cstdint> // int64_t
|
||||
#include <map> // map
|
||||
#include <string> // basic_string
|
||||
#include <type_traits> // enable_if, is_constructible
|
||||
#include <type_traits> // decay, enable_if, is_arithmetic, is_constructible, is_integral, is_same
|
||||
#include <unordered_map> // unordered_map
|
||||
#include <vector> // vector
|
||||
|
||||
@@ -103,6 +104,41 @@ Map map_value(const View& v)
|
||||
template<typename Key>
|
||||
struct is_string_key : std::is_constructible<Key, const char*, std::size_t> {};
|
||||
|
||||
/// the arithmetic types arithmetic_value() converts to (bool has its own
|
||||
/// conversion)
|
||||
template<typename T>
|
||||
struct is_non_bool_arithmetic : conjunction<std::is_arithmetic<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// the default values of value() other than C strings, which have their own
|
||||
/// overload (returning string_t)
|
||||
template<typename T>
|
||||
struct is_non_c_string_default : negation<std::is_same<typename std::decay<T>::type, const char*>> {};
|
||||
|
||||
/// the integral types accepted as an array index by the edits of a document
|
||||
template<typename T>
|
||||
struct is_non_bool_integral : conjunction<std::is_integral<T>, negation<std::is_same<T, bool>>> {};
|
||||
|
||||
/// whether View::get<T>() converts to T without materialize(); get_to()
|
||||
/// assigns these, and passes the others to from_json
|
||||
template<typename T, typename BasicJsonType, typename View>
|
||||
struct is_direct_value : std::integral_constant < bool,
|
||||
std::is_arithmetic<T>::value || std::is_same<T, std::nullptr_t>::value
|
||||
|| std::is_same<T, typename View::string_view_t>::value
|
||||
|| std::is_same<T, BasicJsonType>::value || std::is_same<T, View>::value >
|
||||
{};
|
||||
|
||||
template<typename Traits, typename Alloc, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::basic_string<char, Traits, Alloc>, BasicJsonType, View> : std::true_type {};
|
||||
|
||||
template<typename U, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::vector<U, A>, BasicJsonType, View> : std::true_type {};
|
||||
|
||||
template<typename K, typename V, typename C, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::map<K, V, C, A>, BasicJsonType, View> : is_string_key<K> {};
|
||||
|
||||
template<typename K, typename V, typename H, typename E, typename A, typename BasicJsonType, typename View>
|
||||
struct is_direct_value<std::unordered_map<K, V, H, E, A>, BasicJsonType, View> : is_string_key<K> {};
|
||||
|
||||
} // namespace view
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -31,12 +31,12 @@
|
||||
#include <cstddef> // nullptr_t, size_t // IWYU pragma: keep
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <cstring> // memcpy, strlen
|
||||
#include <istream> // istream
|
||||
#include <istream> // istream // IWYU pragma: keep
|
||||
#include <iterator> // distance, input_iterator_tag, iterator_traits
|
||||
#include <map> // map
|
||||
#include <memory> // unique_ptr
|
||||
#ifndef JSON_NO_IO
|
||||
#include <ostream> // ostream
|
||||
#include <ostream> // ostream // IWYU pragma: keep
|
||||
#endif
|
||||
#include <string> // string
|
||||
#include <tuple> // tuple_element, tuple_size // IWYU pragma: keep
|
||||
@@ -265,7 +265,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_discarded())
|
||||
{
|
||||
return basic_json_view();
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
||||
}
|
||||
@@ -291,7 +291,7 @@ class basic_json_view
|
||||
{
|
||||
if (is_discarded())
|
||||
{
|
||||
return basic_json_view();
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
@@ -314,7 +314,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(is_discarded()))
|
||||
{
|
||||
return basic_json_view();
|
||||
return {};
|
||||
}
|
||||
return detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::unchecked);
|
||||
}
|
||||
@@ -379,7 +379,7 @@ class basic_json_view
|
||||
/// the member with this key converted to T, or the default value if there
|
||||
/// is no such member (the first one, should the key occur more than
|
||||
/// once). Throws type_error.306 if this is not an object.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
T value(string_view_t key, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
@@ -398,7 +398,7 @@ class basic_json_view
|
||||
/// the value a JSON pointer refers to converted to T, or the default
|
||||
/// value if the pointer cannot be resolved. Throws type_error.306 if this
|
||||
/// is neither an object nor an array.
|
||||
template < typename T, typename std::enable_if < !std::is_same<typename std::decay<T>::type, const char*>::value, int >::type = 0 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_c_string_default<T>::value, int>::type = 0>
|
||||
T value(const json_pointer& ptr, const T& default_value) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
@@ -487,11 +487,11 @@ class basic_json_view
|
||||
return !detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::contains).is_discarded();
|
||||
}
|
||||
|
||||
/// 1 if this is an object with a member with this key, else 0 (duplicate
|
||||
/// keys count once)
|
||||
/// the number of members with this key (more than 1 if the key occurs
|
||||
/// more than once); 0 if this is not an object
|
||||
size_type count(string_view_t key) const
|
||||
{
|
||||
return contains(key) ? 1 : 0;
|
||||
return is_object() ? detail::view::count_members<Editable>(*m_doc, m_node, key.data(), key.size()) : 0;
|
||||
}
|
||||
|
||||
size_type count(const char* key) const
|
||||
@@ -558,10 +558,12 @@ class basic_json_view
|
||||
return get_impl(detail::view::value_tag<T> {}, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
/// as BasicJsonType::get_to(): the types get<T>() converts directly are
|
||||
/// assigned, all others are passed to from_json with v as it is
|
||||
template<typename T>
|
||||
T& get_to(T& v) const
|
||||
{
|
||||
v = get<T>();
|
||||
get_to_impl(v, detail::view::is_direct_value<T, BasicJsonType, basic_json_view> {});
|
||||
return v;
|
||||
}
|
||||
|
||||
@@ -748,8 +750,10 @@ class basic_json_view
|
||||
{
|
||||
if (editable() && m_doc->edits != nullptr)
|
||||
{
|
||||
// positions of moved and new values are not source offsets
|
||||
return m_node == m_doc->nodes ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
// positions of moved and new values are not source offsets; the root
|
||||
// (the first node) spans the source text and every edit
|
||||
const bool is_root = m_node == m_doc->nodes;
|
||||
return is_root ? m_doc->size + m_doc->edits->text_used : 64;
|
||||
}
|
||||
const node* const end = m_doc->nodes + m_doc->node_count;
|
||||
if ((m_node->flags & detail::view::node_flags::storage) != 0)
|
||||
@@ -796,7 +800,7 @@ class basic_json_view
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
|
||||
template < typename T, typename std::enable_if < std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, int >::type = 0 >
|
||||
template<typename T, typename std::enable_if<detail::view::is_non_bool_arithmetic<T>::value, int>::type = 0>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T get_impl(detail::view::value_tag<T> /*unused*/, detail::priority_tag<2> /*unused*/) const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
@@ -862,6 +866,21 @@ class basic_json_view
|
||||
return materialize().template get<T>();
|
||||
}
|
||||
|
||||
// --- get_to() dispatch ---
|
||||
|
||||
template<typename T>
|
||||
void get_to_impl(T& v, std::true_type /*direct*/) const
|
||||
{
|
||||
v = get<T>();
|
||||
}
|
||||
|
||||
/// from_json may keep parts of v (members a JSON object lacks, say)
|
||||
template<typename T>
|
||||
void get_to_impl(T& v, std::false_type /*direct*/) const
|
||||
{
|
||||
materialize().get_to(v);
|
||||
}
|
||||
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
};
|
||||
@@ -1021,7 +1040,7 @@ class basic_json_document
|
||||
return m_data ? m_data->node_count : 0;
|
||||
}
|
||||
|
||||
/// bytes held by the document (index, decoded strings, owned text or image)
|
||||
/// bytes held by the document (index, decoded strings, and owned text or image)
|
||||
std::size_t memory_usage() const noexcept
|
||||
{
|
||||
if (!m_data)
|
||||
@@ -1169,7 +1188,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// assign an existing array element; returns a view of it
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
@@ -1191,14 +1210,14 @@ class basic_json_document
|
||||
const auto& token = ptr.back();
|
||||
if (parent.is_null())
|
||||
{
|
||||
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
});
|
||||
if (token == "-")
|
||||
{
|
||||
return push_back(parent, std::forward<V>(value));
|
||||
}
|
||||
const bool digits = std::all_of(token.begin(), token.end(), [](const char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
});
|
||||
if (digits)
|
||||
{
|
||||
// (an invalid index is an error before the parent changes)
|
||||
@@ -1236,7 +1255,7 @@ class basic_json_document
|
||||
|
||||
/// insert into an array before position idx (idx <= size()); returns a
|
||||
/// view of the new element
|
||||
template < typename I, typename V, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type insert(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||
@@ -1249,7 +1268,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// remove an array element
|
||||
template < typename I, typename std::enable_if < std::is_integral<I>::value && !std::is_same<I, bool>::value, int >::type = 0 >
|
||||
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
void erase(view_type array, I idx)
|
||||
{
|
||||
editor().erase(array, index(idx));
|
||||
|
||||
Generated
+568320
File diff suppressed because it is too large.
Load diff
Generated
+1490
-227
File diff suppressed because it is too large.
Load diff
Generated
+40
-4
@@ -71,6 +71,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
|
||||
@@ -119,14 +127,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( \
|
||||
@@ -137,7 +169,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) \
|
||||
|
||||
Generated
+283
-200
File diff suppressed because it is too large.
Load diff
@@ -285,6 +285,10 @@ if (JSON_TestSimdutf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# test-file_input checks from a second thread that parsing from a FILE* releases the file's lock
|
||||
find_package(Threads REQUIRED)
|
||||
json_test_set_test_options("test-file_input;test-file_input_unlocked_io_disabled" LINK_LIBRARIES Threads::Threads)
|
||||
|
||||
# *DO* use json_test_set_test_options() above this line
|
||||
|
||||
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
||||
@@ -369,6 +373,33 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a std::istream again without scanning the get area in bulk
|
||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
||||
NAME test-stream-bulk-scan_disabled
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a std::istream again with JSON_PRECISE_STREAM_POSITION
|
||||
json_test_set_test_options(test-stream-bulk-scan_precise
|
||||
COMPILE_DEFINITIONS JSON_PRECISE_STREAM_POSITION=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-stream-bulk-scan.cpp
|
||||
NAME test-stream-bulk-scan_precise
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# test reading from a FILE* again with the locking stdio functions
|
||||
json_test_set_test_options(test-file_input_unlocked_io_disabled
|
||||
COMPILE_DEFINITIONS JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||
)
|
||||
json_test_add_test_for(src/unit-file_input.cpp
|
||||
NAME test-file_input_unlocked_io_disabled
|
||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||
)
|
||||
|
||||
# the json_view parser again with the portable string scanning instead of
|
||||
# NEON/SSE2, and on x86-64 with the SSSE3 UTF-8 check (JSON_VIEW_USE_SSSE3)
|
||||
json_test_set_test_options(test-json_view_builder_portable
|
||||
|
||||
@@ -21,7 +21,11 @@ foreach(test default noversion)
|
||||
target_compile_definitions(abi_config_${test}_all_tags PRIVATE
|
||||
JSON_DIAGNOSTICS=1
|
||||
JSON_DIAGNOSTIC_POSITIONS=1
|
||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)
|
||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||
JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||
JSON_VIEW_NO_SIMD
|
||||
JSON_VIEW_USE_SSSE3)
|
||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||
add_test(
|
||||
NAME test-abi_config_${test}_all_tags
|
||||
|
||||
@@ -52,6 +52,22 @@ TEST_CASE("default namespace")
|
||||
expected += "_ekmo";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
expected += "_dsbs";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
expected += "_dufio";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
expected += "_vns";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_USE_SSSE3
|
||||
expected += "_vssse3";
|
||||
#endif
|
||||
|
||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
||||
|
||||
@@ -53,6 +53,22 @@ TEST_CASE("default namespace without version component")
|
||||
expected += "_ekmo";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_STREAM_BULK_SCAN
|
||||
expected += "_dsbs";
|
||||
#endif
|
||||
|
||||
#if JSON_DISABLE_UNLOCKED_FILE_IO
|
||||
expected += "_dufio";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_NO_SIMD
|
||||
expected += "_vns";
|
||||
#endif
|
||||
|
||||
#ifdef JSON_VIEW_USE_SSSE3
|
||||
expected += "_vssse3";
|
||||
#endif
|
||||
|
||||
expected += "::basic_json";
|
||||
|
||||
// fallback for Clang
|
||||
|
||||
@@ -22,6 +22,15 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
||||
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
||||
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
||||
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
|
||||
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
|
||||
| `Copy`, `CopyAssign`, `CopyRing` | copy a parsed document and destroy a copy, both timed: a temporary copy, an assignment over an earlier copy, and replacing the oldest of eight live copies |
|
||||
| `CopyOnly`, `DestroyOnly` | only the copy or only the destruction, the other half untimed; a fast destructor can leave work to the next allocations, so these tell the halves apart but do not compare versions |
|
||||
| `Equal`, `Iterate` | compare a parsed document with a copy, or visit every value with range-based `for` loops |
|
||||
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
|
||||
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
|
||||
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
|
||||
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
|
||||
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved, and destroy it again |
|
||||
|
||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||
|
||||
@@ -8,11 +8,16 @@
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <test_data.hpp>
|
||||
|
||||
@@ -51,6 +56,65 @@ BENCHMARK_CAPTURE(ParseFile, signed_ints, TEST_DATA_DIRECTORY "/regression
|
||||
BENCHMARK_CAPTURE(ParseFile, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
||||
BENCHMARK_CAPTURE(ParseFile, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// parse JSON from a FILE*
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ParseFILE(benchmark::State& state, const char* filename)
|
||||
{
|
||||
while (state.KeepRunning())
|
||||
{
|
||||
state.PauseTiming();
|
||||
std::FILE* f = std::fopen(filename, "rb");
|
||||
auto* j = new json();
|
||||
state.ResumeTiming();
|
||||
|
||||
*j = json::parse(f);
|
||||
|
||||
state.PauseTiming();
|
||||
std::fclose(f);
|
||||
delete j;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
state.SetBytesProcessed(state.iterations() * file.tellg());
|
||||
}
|
||||
BENCHMARK_CAPTURE(ParseFILE, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(ParseFILE, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(ParseFILE, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// read many small values from one stream (NDJSON)
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void ParseStreamValues(benchmark::State& state, bool numbers)
|
||||
{
|
||||
std::string input;
|
||||
for (int i = 0; i < 10000; ++i)
|
||||
{
|
||||
input += numbers ? std::to_string(i * 7919) + " "
|
||||
: R"({"id":)" + std::to_string(i) + R"(,"name":"user)" + std::to_string(i) + R"(","active":true,"score":)" + std::to_string(i * 0.25) + "}\n";
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
std::istringstream is(input);
|
||||
json j;
|
||||
std::size_t count = 0;
|
||||
while (!(is >> std::ws).eof())
|
||||
{
|
||||
is >> j;
|
||||
++count;
|
||||
}
|
||||
benchmark::DoNotOptimize(count);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(static_cast<int64_t>(state.iterations() * input.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(ParseStreamValues, ndjson, false);
|
||||
BENCHMARK_CAPTURE(ParseStreamValues, numbers, true);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// parse JSON from string
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -649,6 +713,584 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
||||
|
||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SAX-parse JSON from string
|
||||
//
|
||||
// sax_parse() with a handler that does next to nothing runs the lexer and
|
||||
// parser without building values, so the gap to ParseString above is the cost
|
||||
// of building the DOM.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::string read_file(const char* filename)
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
static json load_json(const char* filename)
|
||||
{
|
||||
return json::parse(read_file(filename));
|
||||
}
|
||||
|
||||
/// SAX handler that only counts the events, so that the handler costs next to nothing
|
||||
class counting_sax : public nlohmann::json_sax<json>
|
||||
{
|
||||
public:
|
||||
std::size_t events = 0;
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool count()
|
||||
{
|
||||
++events;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static void SaxParse(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::string str = read_file(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
counting_sax sax;
|
||||
bool result = json::sax_parse(str, &sax);
|
||||
benchmark::DoNotOptimize(result);
|
||||
benchmark::DoNotOptimize(sax.events);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(state.iterations() * str.size());
|
||||
}
|
||||
BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// work with values: copy, destroy, compare, iterate
|
||||
//
|
||||
// The parsed test files stand in for a document an application keeps around.
|
||||
// Each benchmark times one whole-document operation; setup and cleanup run
|
||||
// outside the timed section.
|
||||
//
|
||||
// A copy is always destroyed again, and how a document is freed shapes the
|
||||
// heap the next copy allocates from: a destructor that frees quickly can leave
|
||||
// work to the allocator's next calls. Copy, CopyAssign and CopyRing therefore
|
||||
// time the copy together with the destruction of a copy; CopyOnly and
|
||||
// DestroyOnly time one half each and are meant for telling the halves apart,
|
||||
// not for comparing versions.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// copy a document and destroy the copy again, as a temporary copy is used
|
||||
static void Copy(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json copy = j;
|
||||
benchmark::DoNotOptimize(copy);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// copy-assign a document over an earlier copy of it, which is freed in the process
|
||||
static void CopyAssign(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
json target = j;
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
target = j;
|
||||
benchmark::DoNotOptimize(target);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(CopyAssign, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(CopyAssign, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(CopyAssign, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// keep eight copies alive and replace the oldest one, as a cache or an undo
|
||||
/// history does, so that the copy is not made from memory just freed by an
|
||||
/// identical document
|
||||
static void CopyRing(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
std::vector<json> ring(8, j);
|
||||
std::size_t oldest = 0;
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
ring[oldest] = j;
|
||||
benchmark::DoNotOptimize(ring[oldest]);
|
||||
oldest = (oldest + 1) % ring.size();
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(CopyRing, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(CopyRing, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(CopyRing, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// only the copy; the copy is destroyed with the timer paused (diagnostic)
|
||||
static void CopyOnly(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto* copy = new json(j);
|
||||
|
||||
state.PauseTiming();
|
||||
delete copy;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(CopyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(CopyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(CopyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// only the destruction; the copy is made with the timer paused (diagnostic)
|
||||
static void DestroyOnly(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
state.PauseTiming();
|
||||
auto* copy = new json(j);
|
||||
state.ResumeTiming();
|
||||
|
||||
delete copy;
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(DestroyOnly, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(DestroyOnly, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(DestroyOnly, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Equal(benchmark::State& state, const char* filename)
|
||||
{
|
||||
// two separate values, so that the comparison cannot stop at identical addresses
|
||||
const json j = load_json(filename);
|
||||
const json copy = j;
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
bool result = (j == copy);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// visit every value with range-based for loops, as client code walks a document
|
||||
static std::size_t count_values(const json& j)
|
||||
{
|
||||
std::size_t result = 1;
|
||||
if (j.is_structured())
|
||||
{
|
||||
for (const auto& element : j)
|
||||
{
|
||||
result += count_values(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void Iterate(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
std::size_t result = count_values(j);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(count_values(j)));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// flatten and unflatten
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void Flatten(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json flat = j.flatten();
|
||||
benchmark::DoNotOptimize(flat);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
// ordered_json objects look keys up linearly, which flatten() used to do for
|
||||
// every value; canada (330,000 values in one flat object) is left out, as it
|
||||
// would take minutes with such versions
|
||||
static void FlattenOrdered(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
nlohmann::ordered_json flat = j.flatten();
|
||||
benchmark::DoNotOptimize(flat);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Unflatten(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json flat = load_json(filename).flatten();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json j = flat.unflatten();
|
||||
benchmark::DoNotOptimize(j);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// diff and patch
|
||||
//
|
||||
// The target is the document with every eighth scalar changed, so the patch
|
||||
// has one "replace" operation per changed value and the structure stays the
|
||||
// same.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void change_every_eighth_scalar(json& j, std::size_t& counter)
|
||||
{
|
||||
if (j.is_structured())
|
||||
{
|
||||
for (auto& element : j)
|
||||
{
|
||||
change_every_eighth_scalar(element, counter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (++counter % 8 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (j.type())
|
||||
{
|
||||
case json::value_t::number_integer:
|
||||
j = j.get<json::number_integer_t>() + 1;
|
||||
break;
|
||||
case json::value_t::number_unsigned:
|
||||
j = j.get<json::number_unsigned_t>() + 1;
|
||||
break;
|
||||
case json::value_t::number_float:
|
||||
j = j.get<json::number_float_t>() + 0.5;
|
||||
break;
|
||||
case json::value_t::string:
|
||||
j = j.get<std::string>() + "!";
|
||||
break;
|
||||
case json::value_t::boolean:
|
||||
j = !j.get<bool>();
|
||||
break;
|
||||
case json::value_t::null:
|
||||
case json::value_t::object:
|
||||
case json::value_t::array:
|
||||
case json::value_t::binary:
|
||||
case json::value_t::discarded:
|
||||
default:
|
||||
j = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static json changed_copy(const json& j)
|
||||
{
|
||||
json result = j;
|
||||
std::size_t counter = 0;
|
||||
change_every_eighth_scalar(result, counter);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void Diff(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json source = load_json(filename);
|
||||
const json target = changed_copy(source);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json patch = json::diff(source, target);
|
||||
benchmark::DoNotOptimize(patch);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(json::diff(source, target).size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Patch(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json source = load_json(filename);
|
||||
const json patch = json::diff(source, changed_copy(source));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json result = source.patch(patch);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(patch.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// JSON Pointer
|
||||
//
|
||||
// The pointers are those of every scalar in the document, i.e., the keys of
|
||||
// its flattened form.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::vector<std::string> pointer_strings(const json& j)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
const json flat = j.flatten();
|
||||
result.reserve(flat.size());
|
||||
for (auto it = flat.begin(); it != flat.end(); ++it)
|
||||
{
|
||||
result.push_back(it.key());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<json::json_pointer> pointers(const json& j)
|
||||
{
|
||||
const std::vector<std::string> strings = pointer_strings(j);
|
||||
std::vector<json::json_pointer> result;
|
||||
result.reserve(strings.size());
|
||||
for (const auto& s : strings)
|
||||
{
|
||||
result.emplace_back(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void JsonPointerParse(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::vector<std::string> strings = pointer_strings(load_json(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& s : strings)
|
||||
{
|
||||
json::json_pointer ptr(s);
|
||||
benchmark::DoNotOptimize(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerToString(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::vector<json::json_pointer> ptrs = pointers(load_json(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
std::string s = ptr.to_string();
|
||||
benchmark::DoNotOptimize(s);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerAt(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
const json& value = j.at(ptr);
|
||||
benchmark::DoNotOptimize(&value);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerContains(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
bool result = j.contains(ptr);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// conversions from and to C++ containers
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void GetVectorInt(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto v = j.get<std::vector<std::int64_t>>();
|
||||
benchmark::DoNotOptimize(v);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json");
|
||||
|
||||
static void GetVectorDouble(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto v = j.get<std::vector<double>>();
|
||||
benchmark::DoNotOptimize(v);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json");
|
||||
|
||||
// a large flat object: the flattened document, one member per scalar
|
||||
static void GetMap(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename).flatten();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto m = j.get<std::map<std::string, json>>();
|
||||
benchmark::DoNotOptimize(m);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
|
||||
/// 100,000 strings, each too long for the small string optimization
|
||||
static std::vector<std::string> make_strings()
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(100000);
|
||||
for (std::size_t i = 0; i < 100000; ++i)
|
||||
{
|
||||
result.push_back("a string that does not fit in place: " + std::to_string(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ToJsonStrings(benchmark::State& state, const bool move)
|
||||
{
|
||||
const std::vector<std::string> strings = make_strings();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
state.PauseTiming();
|
||||
std::vector<std::string> input = strings;
|
||||
state.ResumeTiming();
|
||||
|
||||
// destroyed inside the timed section, like the copies above
|
||||
auto* j = move ? new json(std::move(input)) : new json(input);
|
||||
delete j;
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(ToJsonStrings, copy, false);
|
||||
BENCHMARK_CAPTURE(ToJsonStrings, move, true);
|
||||
|
||||
// conversion of a JSON array into standard containers
|
||||
template<typename Container>
|
||||
static void GetArray(benchmark::State& state)
|
||||
|
||||
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user