mirror of
https://github.com/nlohmann/json.git
synced 2026-10-24 06:48:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ee6351361 | ||
|
|
f8df3df021 | ||
|
|
0e5a906cd5 | ||
|
|
fc347b5779 | ||
|
|
fce5a54722 | ||
|
|
96b5c82496 | ||
|
|
bf27f0ba99 | ||
|
|
7506a1b0bf | ||
|
|
ae59fbb5a4 | ||
|
|
bd7878f899 | ||
|
|
0092111f4d | ||
|
|
c533a107a1 | ||
|
|
d556800512 | ||
|
|
0721d3c389 | ||
|
|
daf104b549 | ||
|
|
643acf23e2 | ||
|
|
df98274f6c | ||
|
|
f503b67ac7 | ||
|
|
e8ab313dde | ||
|
|
727911bc80 | ||
|
|
210a398ce8 | ||
|
|
f266ce92e5 | ||
|
|
92e2e84722 | ||
|
|
0c804e0ad7 | ||
|
|
99aee52002 | ||
|
|
5275930814 | ||
|
|
38147ab676 | ||
|
|
390ebc91b9 | ||
|
|
3a4285ad88 | ||
|
|
8b19b94fd6 | ||
|
|
2e92fe8d26 | ||
|
|
d52cfb7221 | ||
|
|
8262acca0e | ||
|
|
4b3fcd0740 | ||
|
|
35930392d8 | ||
|
|
f8d6aa95eb | ||
|
|
48bd677617 | ||
|
|
379bf8eddb | ||
|
|
c9c9b90f1f | ||
|
|
058d6fd23d | ||
|
|
25e7edad54 | ||
|
|
ffcd392be0 | ||
|
|
e28506dd86 | ||
|
|
03a0595c1e | ||
|
|
4e72af9d54 | ||
|
|
0f5d07310c | ||
|
|
0b924f5375 | ||
|
|
6fa14d8f95 | ||
|
|
dec0e9b749 | ||
|
|
86d5c56c54 | ||
|
|
aa0b371f7c | ||
|
|
ace1030107 | ||
|
|
2dc1e54856 | ||
|
|
9855db81f3 | ||
|
|
533cbfee51 | ||
|
|
9bf531436f | ||
|
|
3e031b06a5 | ||
|
|
7c26e99ac5 | ||
|
|
6aefd8c662 | ||
|
|
52fecc1fab | ||
|
|
fa37b952df | ||
|
|
e9970066c5 | ||
|
|
3f8677d028 | ||
|
|
ef3d350639 | ||
|
|
5e13f834c1 | ||
|
|
e13e976801 | ||
|
|
898e6ee81d | ||
|
|
ef1cdb59ac |
No files matched your search
+3
-10
@@ -108,16 +108,9 @@ The tests are located in [`tests/src/unit-*.cpp`](https://github.com/nlohmann/js
|
|||||||
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
are structured along the features of the library or the nature of the tests. Usually, it should be clear from the
|
||||||
context which existing file needs to be extended, and only very few cases require creating new test files.
|
context which existing file needs to be extended, and only very few cases require creating new test files.
|
||||||
|
|
||||||
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue. Tests that need C++17 or
|
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||||
C++20 go into `unit-regression3-cpp17.cpp` or `unit-regression3-cpp20.cpp` instead, so the large file is not rebuilt
|
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||||
for every C++ standard. `unit-regression2.cpp` holds the older tests; the two files exist because a single one grew
|
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||||
large enough for the MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing
|
|
||||||
the larger one.
|
|
||||||
|
|
||||||
To keep compile times down, several test files are compiled together as one translation unit. This sets a few rules
|
|
||||||
for test files, such as giving file-scope helpers file-specific names. See the README in the
|
|
||||||
[`tests`](https://github.com/nlohmann/json/tree/develop/tests) folder for how the tests are built and what to keep in
|
|
||||||
mind when adding them.
|
|
||||||
|
|
||||||
#### Exceptions
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ environment:
|
|||||||
matrix:
|
matrix:
|
||||||
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
# The Visual Studio 2017 jobs compile everything with /std:c++17, so they
|
||||||
# only build the C++17 variant of each test, split into two jobs each to
|
# only build the C++17 variant of each test, split into two jobs each to
|
||||||
# stay below AppVeyor's 60-minute limit per job.
|
# stay below AppVeyor's 60-minute limit per job. The Visual Studio 2019
|
||||||
|
# job builds the tests for all standards and is split for the same reason.
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
configuration: Debug
|
configuration: Debug
|
||||||
platform: x86
|
platform: x86
|
||||||
@@ -50,7 +51,13 @@ environment:
|
|||||||
configuration: Release
|
configuration: Release
|
||||||
platform: x86
|
platform: x86
|
||||||
CXX_FLAGS: "/W4 /WX"
|
CXX_FLAGS: "/W4 /WX"
|
||||||
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF"
|
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=0/2"
|
||||||
|
GENERATOR: Visual Studio 16 2019
|
||||||
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
|
configuration: Release
|
||||||
|
platform: x86
|
||||||
|
CXX_FLAGS: "/W4 /WX"
|
||||||
|
CMAKE_OPTIONS: "-DJSON_ImplicitConversions=OFF -DJSON_TestShard=1/2"
|
||||||
GENERATOR: Visual Studio 16 2019
|
GENERATOR: Visual Studio 16 2019
|
||||||
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ labels:
|
|||||||
- "tests/src/unit-json_view.*"
|
- "tests/src/unit-json_view.*"
|
||||||
- "tests/src/json_view_test_helpers\\.hpp"
|
- "tests/src/json_view_test_helpers\\.hpp"
|
||||||
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
- "tests/src/fuzzer-(parse_json_view|json_view_image)\\.cpp"
|
||||||
|
- "tests/benchmarks/json_view/.*"
|
||||||
- "tools/amalgamate/config_json_view\\.json"
|
- "tools/amalgamate/config_json_view\\.json"
|
||||||
- "docs/mkdocs/docs/features/json_view\\.md"
|
- "docs/mkdocs/docs/features/json_view\\.md"
|
||||||
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
- "docs/mkdocs/docs/api/basic_json_(document|view)/.*"
|
||||||
|
|||||||
@@ -154,6 +154,28 @@ jobs:
|
|||||||
# PR content in a job without credentials.
|
# PR content in a job without credentials.
|
||||||
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
python3 tools/generate_natvis/generate_natvis.py $MAIN_DIR
|
||||||
|
|
||||||
|
- name: Check the nlohmann_json.natvis generator options
|
||||||
|
run: |
|
||||||
|
cd $MAIN_DIR
|
||||||
|
# The options for configurations outside the checked-in file write
|
||||||
|
# to a temporary file, so they do not affect the diff below.
|
||||||
|
GENERATOR=tools/generate_natvis/generate_natvis.py
|
||||||
|
python3 $GENERATOR --list-tags
|
||||||
|
TAG_COUNT=$(python3 $GENERATOR --list-tags | wc -l)
|
||||||
|
|
||||||
|
python3 $GENERATOR --abi-tags diag,_dp,_vns --abi-tags _diag --output $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns -->' $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
grep -q '<!-- Namespace nlohmann::json_abi_diag_dp_vns_v[0-9_]* -->' $RUNNER_TEMP/abi_tags.natvis
|
||||||
|
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/abi_tags.natvis) -eq $(( $(grep -c '<!-- Namespace ' nlohmann_json.natvis) + 2 ))
|
||||||
|
|
||||||
|
python3 $GENERATOR --all --output $RUNNER_TEMP/all.natvis
|
||||||
|
test $(grep -c '<!-- Namespace ' $RUNNER_TEMP/all.natvis) -eq $(( (1 << TAG_COUNT) * 2 + 1 ))
|
||||||
|
|
||||||
|
if python3 $GENERATOR --abi-tags _unknown --output $RUNNER_TEMP/unknown.natvis; then
|
||||||
|
echo "generate_natvis.py accepted an unknown ABI tag"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
id: diff
|
id: diff
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
@@ -37,6 +37,14 @@ jobs:
|
|||||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||||
return artifact.name == "pr"
|
return artifact.name == "pr"
|
||||||
})[0];
|
})[0];
|
||||||
|
// a run that failed before its jobs started (e.g. a stale queued
|
||||||
|
// run cancelled long after its push) has no artifact to report on
|
||||||
|
if (!matchArtifact) {
|
||||||
|
core.notice('Run ${{ github.event.workflow_run.id }} has no "pr" artifact; nothing to comment on.');
|
||||||
|
core.setOutput('found', 'false');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
core.setOutput('found', 'true');
|
||||||
var download = await github.rest.actions.downloadArtifact({
|
var download = await github.rest.actions.downloadArtifact({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
@@ -51,9 +59,11 @@ jobs:
|
|||||||
# Extract the untrusted PR artifact into a dedicated empty directory and
|
# Extract the untrusted PR artifact into a dedicated empty directory and
|
||||||
# read only the two expected files by fixed path afterwards. This avoids a
|
# read only the two expected files by fixed path afterwards. This avoids a
|
||||||
# malicious archive overwriting workspace files or escaping via ../ paths.
|
# malicious archive overwriting workspace files or escaping via ../ paths.
|
||||||
- run: unzip -o pr.zip -d ./pr_artifact
|
- if: steps.download.outputs.found == 'true'
|
||||||
|
run: unzip -o pr.zip -d ./pr_artifact
|
||||||
|
|
||||||
- name: 'Comment on PR'
|
- name: 'Comment on PR'
|
||||||
|
if: steps.download.outputs.found == 'true'
|
||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -47,6 +47,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
name: "json_view benchmarks"
|
||||||
|
|
||||||
|
# On demand only: runs the comparison of json_view with yyjson, simdjson, and
|
||||||
|
# Boost.JSON (tests/benchmarks/json_view/compare.py) on GitHub-hosted runners,
|
||||||
|
# for numbers from x86-64 and AArch64 Linux. It runs when started by hand, or
|
||||||
|
# when a pull request gets the label "benchmark" (on both architectures, with
|
||||||
|
# GCC and the default settings). Shared runners are noisy: the results show
|
||||||
|
# where json_view stands, but published numbers need a quiet machine (see
|
||||||
|
# tests/benchmarks/json_view/README.md).
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [labeled]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
runner:
|
||||||
|
description: "Runner image"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- ubuntu-24.04
|
||||||
|
- ubuntu-24.04-arm
|
||||||
|
default: ubuntu-24.04
|
||||||
|
compiler:
|
||||||
|
description: "Compiler"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- g++
|
||||||
|
- clang++
|
||||||
|
default: g++
|
||||||
|
native:
|
||||||
|
description: "Compile for the runner's CPU (-march=native)"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
rounds:
|
||||||
|
description: "Rounds of bench_view"
|
||||||
|
type: number
|
||||||
|
default: 30
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
# (the label is part of the group: adding an unrelated label must not cancel a running benchmark)
|
||||||
|
group: json-view-benchmarks-${{ github.workflow }}-${{ github.ref }}-${{ github.event.label.name }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
compare:
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.event.label.name == 'benchmark'
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
runner: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', inputs.runner) || '["ubuntu-24.04", "ubuntu-24.04-arm"]') }}
|
||||||
|
runs-on: ${{ matrix.runner }}
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Download test data
|
||||||
|
run: |
|
||||||
|
cmake -S . -B build -DJSON_BuildTests=On
|
||||||
|
cmake --build build --target download_test_data
|
||||||
|
|
||||||
|
- name: Run the comparison
|
||||||
|
env:
|
||||||
|
CXX: ${{ inputs.compiler || 'g++' }}
|
||||||
|
CC: ${{ inputs.compiler == 'clang++' && 'clang' || 'gcc' }}
|
||||||
|
ROUNDS: ${{ inputs.rounds || 30 }}
|
||||||
|
NATIVE: ${{ inputs.native && '--native' || '' }}
|
||||||
|
run: python3 tests/benchmarks/json_view/compare.py --data build/test_files --download --rounds "$ROUNDS" $NATIVE
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: cat tests/benchmarks/json_view/results/*.md >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||||
|
with:
|
||||||
|
name: json_view-benchmarks-${{ matrix.runner }}-${{ inputs.compiler || 'g++' }}
|
||||||
|
path: tests/benchmarks/json_view/results/
|
||||||
@@ -80,6 +80,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
@@ -37,6 +37,7 @@ cc_library(
|
|||||||
"include/nlohmann/detail/input/parser.hpp",
|
"include/nlohmann/detail/input/parser.hpp",
|
||||||
"include/nlohmann/detail/input/position_t.hpp",
|
"include/nlohmann/detail/input/position_t.hpp",
|
||||||
"include/nlohmann/detail/input/pow5_table.hpp",
|
"include/nlohmann/detail/input/pow5_table.hpp",
|
||||||
|
"include/nlohmann/detail/input/simd_scan.hpp",
|
||||||
"include/nlohmann/detail/input/string_scan.hpp",
|
"include/nlohmann/detail/input/string_scan.hpp",
|
||||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ This file describes the source for supporting files; that is, files that are not
|
|||||||
- [GitHub](#github)
|
- [GitHub](#github)
|
||||||
- [REUSE](#reuse)
|
- [REUSE](#reuse)
|
||||||
- [Package Managers](#package-managers)
|
- [Package Managers](#package-managers)
|
||||||
|
- [Debugging](#debugging)
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
@@ -289,3 +290,29 @@ The module definition for [Bazel](https://bazel.build)'s [Bzlmod](https://bazel.
|
|||||||
Further documentation:
|
Further documentation:
|
||||||
|
|
||||||
- [Bazel modules](https://bazel.build/external/module)
|
- [Bazel modules](https://bazel.build/external/module)
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### `nlohmann_json.natvis`
|
||||||
|
|
||||||
|
The [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects) file that
|
||||||
|
gives `json` values a friendly view in the Visual Studio debugger. The file is generated by
|
||||||
|
`tools/generate_natvis/generate_natvis.py` from the template `tools/generate_natvis/nlohmann_json.natvis.j2` and the ABI
|
||||||
|
tags and version in `include/nlohmann/detail/abi_macros.hpp`; change the script or the template rather than editing
|
||||||
|
the file by hand. The file can be updated by calling
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis
|
||||||
|
```
|
||||||
|
|
||||||
|
The "Check amalgamation" workflow fails if the file is out of date.
|
||||||
|
|
||||||
|
The file covers the namespace variants with up to two ABI tags. A Natvis file for other configurations can be
|
||||||
|
generated with the `--abi-tags` or `--all` options, for example
|
||||||
|
`make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"`; see
|
||||||
|
[`tools/generate_natvis`](tools/generate_natvis/README.md). With MSVC, CMake adds the file to the `nlohmann_json`
|
||||||
|
target and installs it.
|
||||||
|
|
||||||
|
Further documentation:
|
||||||
|
|
||||||
|
- [Create custom views of C++ objects in the debugger using the Natvis framework](https://learn.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects)
|
||||||
@@ -46,7 +46,7 @@ all:
|
|||||||
@echo "clean - remove built files"
|
@echo "clean - remove built files"
|
||||||
@echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers"
|
@echo "(fuzzers) - see tests/fuzzing.md, or run 'make -C tests fuzzers' to build the AFL fuzzers"
|
||||||
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
@echo "macro_builder_check - check that macro_scope.hpp matches tools/macro_builder's output"
|
||||||
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version"
|
@echo "natvis - regenerate nlohmann_json.natvis from the current ABI tags and version (pass generator options via NATVIS_ARGS, see tools/generate_natvis/README.md)"
|
||||||
@echo "pretty - beautify code with Artistic Style"
|
@echo "pretty - beautify code with Artistic Style"
|
||||||
@echo "pvs_studio - run the PVS-Studio static analyzer and open the report"
|
@echo "pvs_studio - run the PVS-Studio static analyzer and open the report"
|
||||||
@echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)"
|
@echo "release - create the release files (include.zip, json.tar.xz, single headers, signatures, hashes)"
|
||||||
@@ -120,13 +120,15 @@ $(AMALGAMATED_VIEW_FILE): $(SRCS)
|
|||||||
tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes
|
tools/amalgamate/amalgamate.py -c tools/amalgamate/config_json_view.json -s . --verbose=yes
|
||||||
|
|
||||||
NATVIS_VENV=tools/generate_natvis/venv
|
NATVIS_VENV=tools/generate_natvis/venv
|
||||||
|
# extra options for generate_natvis.py, e.g. NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my.natvis"
|
||||||
|
NATVIS_ARGS=
|
||||||
|
|
||||||
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
# regenerate nlohmann_json.natvis from the ABI tags and version in include/nlohmann/detail/abi_macros.hpp
|
||||||
# (the dependencies are installed into a virtual environment first)
|
# (the dependencies are installed into a virtual environment first)
|
||||||
natvis:
|
natvis:
|
||||||
@test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV)
|
@test -d $(NATVIS_VENV) || python3 -mvenv $(NATVIS_VENV)
|
||||||
@$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt
|
@$(NATVIS_VENV)/bin/python3 -c 'import jinja2' 2>/dev/null || $(NATVIS_VENV)/bin/pip3 install --quiet -r tools/generate_natvis/requirements.txt
|
||||||
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py .
|
$(NATVIS_VENV)/bin/python3 tools/generate_natvis/generate_natvis.py $(NATVIS_ARGS) .
|
||||||
|
|
||||||
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
# regenerate the two tools/macro_builder blocks of $(MACRO_SCOPE_HPP) (see its README.md) and diff against the
|
||||||
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
# checked-in header; phony, because it never writes $(MACRO_SCOPE_HPP) itself
|
||||||
|
|||||||
+3
-3
@@ -890,11 +890,11 @@ add_custom_target(ci_icpx
|
|||||||
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not
|
# The following tests are excluded as they trigger known nvc++ 25.5 defects (not
|
||||||
# library bugs); see https://github.com/nlohmann/json for tracking. Only the
|
# library bugs); see https://github.com/nlohmann/json for tracking. Only the
|
||||||
# affected language-standard variants are excluded so coverage is otherwise kept:
|
# affected language-standard variants are excluded so coverage is otherwise kept:
|
||||||
# - test-comparison-cpp20_cpp20, test-comparison_legacy-cpp20_cpp20
|
# - test-comparison_cpp20, test-comparison_legacy_cpp20
|
||||||
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
|
# miscompiles cross-type/<=> comparison (e.g. `-17 <= null`)
|
||||||
# - test-constructor1_cpp11
|
# - test-constructor1_cpp11
|
||||||
# std::initializer_list lifetime bug -> SIGSEGV
|
# std::initializer_list lifetime bug -> SIGSEGV
|
||||||
# - test-deserialization-cpp20_cpp20
|
# - test-deserialization_cpp20
|
||||||
# mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
|
# mangles the UTF-8 u8"" string literal in the char8_t (C++20) section
|
||||||
add_custom_target(ci_nvhpc
|
add_custom_target(ci_nvhpc
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
@@ -906,7 +906,7 @@ add_custom_target(ci_nvhpc
|
|||||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_nvhpc
|
||||||
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
# the pipes are escaped so the surrounding shell passes them to ctest verbatim
|
||||||
# instead of treating them as shell pipe operators
|
# instead of treating them as shell pipe operators
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison-cpp20_cpp20\\|test-comparison_legacy-cpp20_cpp20\\|test-constructor1_cpp11\\|test-deserialization-cpp20_cpp20" --output-on-failure
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_nvhpc && ${CMAKE_CTEST_COMMAND} --parallel ${N} --exclude-regex "test-comparison_cpp20\\|test-comparison_legacy_cpp20\\|test-constructor1_cpp11\\|test-deserialization_cpp20" --output-on-failure
|
||||||
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
COMMENT "Compile and test with NVIDIA HPC SDK (nvc++)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+39
-366
@@ -109,55 +109,6 @@ function(_json_test_apply_test_properties test_target properties_target)
|
|||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# for internal use by _json_test_add_test() and _json_test_add_unity_batch():
|
|
||||||
# registers the CTest test <test_name>_cpp<cxx_standard> (plus its Valgrind
|
|
||||||
# variant), which runs the executable target <test_target> with the arguments
|
|
||||||
# in ARGN, and applies the test properties of the test- and standard-specific
|
|
||||||
# interface targets
|
|
||||||
function(_json_test_register_test test_name test_target cxx_standard)
|
|
||||||
set(ctest_name ${test_name}_cpp${cxx_standard})
|
|
||||||
|
|
||||||
if (JSON_FastTests)
|
|
||||||
add_test(NAME ${ctest_name}
|
|
||||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN}
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
add_test(NAME ${ctest_name}
|
|
||||||
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} ${ARGN} --no-skip
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
set_tests_properties(${ctest_name} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
|
||||||
|
|
||||||
# apply standard-specific test properties
|
|
||||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
|
||||||
_json_test_apply_test_properties(${ctest_name} _json_test_interface__cpp_${cxx_standard})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# apply test-specific test properties
|
|
||||||
if(TARGET _json_test_interface_${test_name})
|
|
||||||
_json_test_apply_test_properties(${ctest_name} _json_test_interface_${test_name})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# apply test- and standard-specific test properties
|
|
||||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
|
||||||
_json_test_apply_test_properties(${ctest_name}
|
|
||||||
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(JSON_Valgrind)
|
|
||||||
add_test(NAME ${ctest_name}_valgrind
|
|
||||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER} ${ARGN}
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
)
|
|
||||||
set_tests_properties(${ctest_name}_valgrind PROPERTIES
|
|
||||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# for internal use by json_test_add_test_for()
|
# for internal use by json_test_add_test_for()
|
||||||
function(_json_test_add_test test_name file main cxx_standard)
|
function(_json_test_add_test test_name file main cxx_standard)
|
||||||
set(test_target ${test_name}_cpp${cxx_standard})
|
set(test_target ${test_name}_cpp${cxx_standard})
|
||||||
@@ -192,7 +143,45 @@ function(_json_test_add_test test_name file main cxx_standard)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
_json_test_register_test(${test_name} ${test_target} ${cxx_standard})
|
if (JSON_FastTests)
|
||||||
|
add_test(NAME ${test_target}
|
||||||
|
COMMAND ${test_target} ${DOCTEST_TEST_FILTER}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
add_test(NAME ${test_target}
|
||||||
|
COMMAND ${test_target} ${DOCTEST_TEST_FILTER} --no-skip
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
set_tests_properties(${test_target} PROPERTIES LABELS "all" FIXTURES_REQUIRED TEST_DATA)
|
||||||
|
|
||||||
|
# apply standard-specific test properties
|
||||||
|
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
||||||
|
_json_test_apply_test_properties(${test_target} _json_test_interface__cpp_${cxx_standard})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# apply test-specific test properties
|
||||||
|
if(TARGET _json_test_interface_${test_name})
|
||||||
|
_json_test_apply_test_properties(${test_target} _json_test_interface_${test_name})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# apply test- and standard-specific test properties
|
||||||
|
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
||||||
|
_json_test_apply_test_properties(${test_target}
|
||||||
|
_json_test_interface_${test_name}_cpp_${cxx_standard}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(JSON_Valgrind)
|
||||||
|
add_test(NAME ${test_target}_valgrind
|
||||||
|
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
set_tests_properties(${test_target}_valgrind PROPERTIES
|
||||||
|
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||||
|
)
|
||||||
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -211,14 +200,6 @@ endfunction()
|
|||||||
# Use NAME <name> to override the filename-derived test name.
|
# Use NAME <name> to override the filename-derived test name.
|
||||||
# Use FORCE to create the test regardless of the file containing
|
# Use FORCE to create the test regardless of the file containing
|
||||||
# JSON_HAS_CPP_<version_number>.
|
# JSON_HAS_CPP_<version_number>.
|
||||||
#
|
|
||||||
# Tests that depend on the C++ standard (e.g., because they use the macros
|
|
||||||
# JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
|
||||||
# should not make the whole of a large unit-foo.cpp be rebuilt for every
|
|
||||||
# standard. Put them into a separate file unit-foo-cpp<NN>.cpp (see, e.g.,
|
|
||||||
# unit-items-cpp17.cpp) which wraps its content in #ifdef JSON_HAS_CPP_<NN>.
|
|
||||||
# Then, unit-foo.cpp itself contains no JSON_HAS_CPP_<NN> and is only built for
|
|
||||||
# C++11.
|
|
||||||
# Test targets are linked against <main>.
|
# Test targets are linked against <main>.
|
||||||
# CXX_STANDARDS defaults to "11".
|
# CXX_STANDARDS defaults to "11".
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -262,314 +243,6 @@ function(json_test_add_test_for file)
|
|||||||
endforeach()
|
endforeach()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# for internal use by json_test_add_unity_tests(): sets <result> to whether the
|
|
||||||
# (absolute) <file> is built for <cxx_standard>; same rule as in
|
|
||||||
# json_test_add_test_for(): C++11 always, others only if the file contains
|
|
||||||
# JSON_HAS_CPP_<cxx_standard> or <force> is set
|
|
||||||
function(_json_test_unity_applies file cxx_standard force result)
|
|
||||||
set(${result} TRUE PARENT_SCOPE)
|
|
||||||
if(NOT ("${cxx_standard}" STREQUAL 11 OR force))
|
|
||||||
file(READ ${file} file_content)
|
|
||||||
string(FIND "${file_content}" JSON_HAS_CPP_${cxx_standard} has_cpp_found)
|
|
||||||
if(${has_cpp_found} EQUAL -1)
|
|
||||||
set(${result} FALSE PARENT_SCOPE)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# for internal use by json_test_add_unity_tests(): creates the executable
|
|
||||||
# test-unity-<batch_name>_cpp<cxx_standard> from the (absolute) source files
|
|
||||||
# in ARGN, which are #include-d by a generated source file, and registers one
|
|
||||||
# CTest test per source file that runs only the test cases of that file; if
|
|
||||||
# <private> is true, the generated file defines JSON_TESTS_PRIVATE first
|
|
||||||
function(_json_test_add_unity_batch batch_name cxx_standard main private)
|
|
||||||
set(batch_target test-unity-${batch_name}_cpp${cxx_standard})
|
|
||||||
set(batch_source ${PROJECT_BINARY_DIR}/tests/unity/${batch_target}.cpp)
|
|
||||||
|
|
||||||
set(batch_content "// generated by cmake/test.cmake; do not edit\n")
|
|
||||||
if(private)
|
|
||||||
string(APPEND batch_content "// at least one file of this batch needs access to private members of the library\n")
|
|
||||||
string(APPEND batch_content "#define JSON_TESTS_PRIVATE\n")
|
|
||||||
# the files define the macro again; mark this definition as used, as
|
|
||||||
# -Wunused-macros reports an unused definition when it is redefined
|
|
||||||
string(APPEND batch_content "#ifdef JSON_TESTS_PRIVATE\n#endif\n")
|
|
||||||
endif()
|
|
||||||
foreach(file ${ARGN})
|
|
||||||
string(APPEND batch_content "#include \"${file}\" // NOLINT(bugprone-suspicious-include)\n")
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# only touch the generated file if it changed to keep incremental builds incremental
|
|
||||||
set(old_content "")
|
|
||||||
if(EXISTS ${batch_source})
|
|
||||||
file(READ ${batch_source} old_content)
|
|
||||||
endif()
|
|
||||||
if(NOT "${old_content}" STREQUAL "${batch_content}")
|
|
||||||
file(WRITE ${batch_source} "${batch_content}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(${batch_target} ${batch_source})
|
|
||||||
target_link_libraries(${batch_target} PRIVATE ${main})
|
|
||||||
set_target_properties(${batch_target} PROPERTIES
|
|
||||||
CXX_STANDARD ${cxx_standard}
|
|
||||||
CXX_STANDARD_REQUIRED ON
|
|
||||||
)
|
|
||||||
if(TARGET _json_test_interface__cpp_${cxx_standard})
|
|
||||||
target_link_libraries(${batch_target} PRIVATE _json_test_interface__cpp_${cxx_standard})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# rebuild the batch when one of its files changes, and show the files in IDEs;
|
|
||||||
# files that are also built standalone (VARIANT_FILES) are left out, as
|
|
||||||
# HEADER_FILE_ONLY is a per-file property and would also affect those targets
|
|
||||||
set_source_files_properties(${batch_source} PROPERTIES OBJECT_DEPENDS "${ARGN}")
|
|
||||||
foreach(file ${ARGN})
|
|
||||||
if(NOT file IN_LIST _json_test_unity_variant_files)
|
|
||||||
set_source_files_properties(${file} PROPERTIES HEADER_FILE_ONLY ON)
|
|
||||||
target_sources(${batch_target} PRIVATE ${file})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
foreach(file ${ARGN})
|
|
||||||
get_filename_component(file_basename ${file} NAME_WE)
|
|
||||||
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
|
||||||
|
|
||||||
# run only the test cases defined in this file (and in the shared
|
|
||||||
# make_test_data_available.hpp if the file uses it)
|
|
||||||
file(READ ${file} file_content)
|
|
||||||
set(source_filter "--source-file=*${file_basename}.cpp")
|
|
||||||
string(FIND "${file_content}" make_test_data_available.hpp uses_test_data)
|
|
||||||
if(NOT ${uses_test_data} EQUAL -1)
|
|
||||||
string(APPEND source_filter ",*make_test_data_available.hpp")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
_json_test_register_test(${test_name} ${batch_target} ${cxx_standard} "${source_filter}")
|
|
||||||
endforeach()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
#############################################################################
|
|
||||||
# json_test_add_unity_tests(
|
|
||||||
# FILES <files>...
|
|
||||||
# MAIN <main>
|
|
||||||
# [CXX_STANDARDS <version_number>...] [FORCE]
|
|
||||||
# [BATCH_SIZE <size>]
|
|
||||||
# [GROUPS <group>...]
|
|
||||||
# [VARIANT_FILES <files>...])
|
|
||||||
#
|
|
||||||
# Like calling json_test_add_test_for(<file> MAIN <main> ...) for each of the
|
|
||||||
# <files>, but compiles several files together to speed up the build: for each
|
|
||||||
# C++ standard, the files are split into batches of <size> files (default: 8)
|
|
||||||
# and each batch is built as a single executable
|
|
||||||
#
|
|
||||||
# test-unity-<pool><index>_cpp<version_number>
|
|
||||||
#
|
|
||||||
# whose generated source file #include-s the files of the batch (so they share
|
|
||||||
# the template instantiations of the library). The tests are still named
|
|
||||||
# test-foo_cpp<version_number>, one per file, but run the batch executable with
|
|
||||||
# a doctest filter that selects the test cases of that file only.
|
|
||||||
#
|
|
||||||
# Files are only batched with files that agree on the macros defined before
|
|
||||||
# the library is included: files that define at most JSON_TESTS_PRIVATE (or
|
|
||||||
# macros derived from global compile definitions) form the pools "plain" and
|
|
||||||
# "private". All other files are added with json_test_add_test_for() as usual,
|
|
||||||
# as are files with test-specific build settings (see
|
|
||||||
# json_test_set_test_options()) and the files listed in the explicit
|
|
||||||
# exclusion list below.
|
|
||||||
# Each <group> names a list variable json_test_unity_group_<group> of test file
|
|
||||||
# stems (file names without "unit-" and ".cpp"). The batchable files of a group
|
|
||||||
# are compiled together (regardless of BATCH_SIZE) as one executable
|
|
||||||
#
|
|
||||||
# test-unity-<group>_cpp<version_number>
|
|
||||||
#
|
|
||||||
# so that related tests, which instantiate the same templates, share one
|
|
||||||
# translation unit. A group may mix the pools "plain" and "private"; if any of
|
|
||||||
# its files needs JSON_TESTS_PRIVATE, the whole group is built with it. Files
|
|
||||||
# that cannot be batched stay standalone even if they are listed in a group.
|
|
||||||
# Files in no group are batched by BATCH_SIZE as described above.
|
|
||||||
# <files> in VARIANT_FILES are also built standalone with other settings, so
|
|
||||||
# they are not marked as header-only sources of the batch.
|
|
||||||
#############################################################################
|
|
||||||
|
|
||||||
function(json_test_add_unity_tests)
|
|
||||||
cmake_parse_arguments(args "FORCE" "MAIN;BATCH_SIZE" "FILES;CXX_STANDARDS;VARIANT_FILES;GROUPS" ${ARGN})
|
|
||||||
|
|
||||||
if("${args_MAIN}" STREQUAL "")
|
|
||||||
message(FATAL_ERROR "Required argument MAIN <main> missing.")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if("${args_BATCH_SIZE}" STREQUAL "")
|
|
||||||
set(args_BATCH_SIZE 8)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if("${args_CXX_STANDARDS}" STREQUAL "")
|
|
||||||
set(args_CXX_STANDARDS 11)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(args_FORCE)
|
|
||||||
set(force FORCE)
|
|
||||||
else()
|
|
||||||
set(force "")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(_json_test_unity_variant_files "")
|
|
||||||
foreach(file ${args_VARIANT_FILES})
|
|
||||||
get_filename_component(file ${file} ABSOLUTE)
|
|
||||||
list(APPEND _json_test_unity_variant_files ${file})
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# files that must not be merged into a batch: unit-32bit.cpp is only built
|
|
||||||
# for 32bit targets, and unit-no-macro-leak.cpp checks that including the
|
|
||||||
# library defines no unprefixed macro, which any other file would disturb
|
|
||||||
set(standalone_files unit-32bit.cpp unit-no-macro-leak.cpp)
|
|
||||||
|
|
||||||
set(harmless_macros "^(DOCTEST_.*|SKIP_TESTS_FOR_.*|JSON_TEST_DEPRECATED_FUNCTIONS_DELETED|JSON_TEST_STRICT_NUL_HANDLING_ENABLED|JSON_TEST_STRINGIZE)$")
|
|
||||||
|
|
||||||
# classify the files
|
|
||||||
set(plain_files "")
|
|
||||||
set(private_files "")
|
|
||||||
set(standalone_abs_files "")
|
|
||||||
foreach(file ${args_FILES})
|
|
||||||
get_filename_component(file_name ${file} NAME)
|
|
||||||
get_filename_component(file_basename ${file} NAME_WE)
|
|
||||||
string(REGEX REPLACE "unit-(.+)" "test-\\1" test_name ${file_basename})
|
|
||||||
|
|
||||||
set(batchable TRUE)
|
|
||||||
if(file_name IN_LIST standalone_files)
|
|
||||||
set(batchable FALSE)
|
|
||||||
endif()
|
|
||||||
if(TARGET _json_test_interface_${test_name})
|
|
||||||
set(batchable FALSE)
|
|
||||||
endif()
|
|
||||||
foreach(cxx_standard ${args_CXX_STANDARDS})
|
|
||||||
if(TARGET _json_test_interface_${test_name}_cpp_${cxx_standard})
|
|
||||||
set(batchable FALSE)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# files that suppress GCC's -Wnoexcept around their include of the
|
|
||||||
# library: the suppression has no effect once another file of the
|
|
||||||
# batch included the library first
|
|
||||||
if(batchable)
|
|
||||||
file(READ ${file} file_content)
|
|
||||||
string(FIND "${file_content}" "SUPPRESS_WARNING(\"-Wnoexcept\")" suppresses_noexcept)
|
|
||||||
if(NOT ${suppresses_noexcept} EQUAL -1)
|
|
||||||
set(batchable FALSE)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(pool plain)
|
|
||||||
if(batchable)
|
|
||||||
# collect the macros (un)defined before the library is included
|
|
||||||
file(READ ${file} file_content)
|
|
||||||
string(FIND "${file_content}" "#include <nlohmann/" include_position)
|
|
||||||
if(NOT ${include_position} EQUAL -1)
|
|
||||||
string(SUBSTRING "${file_content}" 0 ${include_position} file_content)
|
|
||||||
endif()
|
|
||||||
string(REGEX MATCHALL "(^|\n)[ \t]*#[ \t]*(define|undef)[ \t]+[A-Za-z_0-9]+" directives "${file_content}")
|
|
||||||
foreach(directive ${directives})
|
|
||||||
string(REGEX REPLACE "^.*[ \t]([A-Za-z_0-9]+)$" "\\1" macro "${directive}")
|
|
||||||
if(macro MATCHES "${harmless_macros}")
|
|
||||||
continue()
|
|
||||||
elseif("${macro}" STREQUAL JSON_TESTS_PRIVATE)
|
|
||||||
set(pool private)
|
|
||||||
else()
|
|
||||||
set(batchable FALSE)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_filename_component(file_abs ${file} ABSOLUTE)
|
|
||||||
if(NOT batchable)
|
|
||||||
list(APPEND standalone_abs_files ${file_abs})
|
|
||||||
json_test_add_test_for(${file} MAIN ${args_MAIN} CXX_STANDARDS ${args_CXX_STANDARDS} ${force})
|
|
||||||
continue()
|
|
||||||
endif()
|
|
||||||
get_filename_component(file ${file} ABSOLUTE)
|
|
||||||
list(APPEND ${pool}_files ${file})
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# resolve the explicit groups: group_<name>_files are the (absolute) files
|
|
||||||
# of the group, group_<name>_private tells whether one of them is private
|
|
||||||
set(grouped_files "")
|
|
||||||
foreach(group ${args_GROUPS})
|
|
||||||
if(NOT DEFINED json_test_unity_group_${group})
|
|
||||||
message(FATAL_ERROR "Unity test group '${group}' is not defined (json_test_unity_group_${group}).")
|
|
||||||
endif()
|
|
||||||
set(group_${group}_files "")
|
|
||||||
set(group_${group}_private FALSE)
|
|
||||||
foreach(stem ${json_test_unity_group_${group}})
|
|
||||||
# check against the source directory, because FILES may be filtered (JSON_TestShard)
|
|
||||||
get_filename_component(file ${CMAKE_CURRENT_SOURCE_DIR}/src/unit-${stem}.cpp ABSOLUTE)
|
|
||||||
if(NOT EXISTS ${file})
|
|
||||||
message(FATAL_ERROR "Unity test group '${group}' lists '${stem}', but ${file} does not exist.")
|
|
||||||
endif()
|
|
||||||
if(file IN_LIST grouped_files)
|
|
||||||
message(FATAL_ERROR "Unity test file unit-${stem}.cpp is listed in more than one group (second: '${group}').")
|
|
||||||
endif()
|
|
||||||
list(APPEND grouped_files ${file})
|
|
||||||
|
|
||||||
if(file IN_LIST standalone_abs_files)
|
|
||||||
message(STATUS "Unity test group '${group}': unit-${stem}.cpp cannot be batched and stays standalone")
|
|
||||||
elseif(file IN_LIST plain_files)
|
|
||||||
list(APPEND group_${group}_files ${file})
|
|
||||||
list(REMOVE_ITEM plain_files ${file})
|
|
||||||
elseif(file IN_LIST private_files)
|
|
||||||
list(APPEND group_${group}_files ${file})
|
|
||||||
list(REMOVE_ITEM private_files ${file})
|
|
||||||
set(group_${group}_private TRUE)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
foreach(cxx_standard ${args_CXX_STANDARDS})
|
|
||||||
if(NOT compiler_supports_cpp_${cxx_standard})
|
|
||||||
continue()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# explicit groups: one batch per group
|
|
||||||
foreach(group ${args_GROUPS})
|
|
||||||
set(batch_files "")
|
|
||||||
foreach(file ${group_${group}_files})
|
|
||||||
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
|
||||||
if(applies)
|
|
||||||
list(APPEND batch_files ${file})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
if(batch_files)
|
|
||||||
_json_test_add_unity_batch(${group} ${cxx_standard} ${args_MAIN} ${group_${group}_private} ${batch_files})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
|
|
||||||
# remaining files: batches of BATCH_SIZE files per pool
|
|
||||||
foreach(pool plain private)
|
|
||||||
set(is_private FALSE)
|
|
||||||
if(pool STREQUAL private)
|
|
||||||
set(is_private TRUE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(batch_files "")
|
|
||||||
set(batch_count 0)
|
|
||||||
set(batch_index 0)
|
|
||||||
foreach(file ${${pool}_files})
|
|
||||||
_json_test_unity_applies(${file} ${cxx_standard} "${force}" applies)
|
|
||||||
if(NOT applies)
|
|
||||||
continue()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
list(APPEND batch_files ${file})
|
|
||||||
math(EXPR batch_count "${batch_count} + 1")
|
|
||||||
if(batch_count EQUAL args_BATCH_SIZE)
|
|
||||||
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
|
||||||
set(batch_files "")
|
|
||||||
set(batch_count 0)
|
|
||||||
math(EXPR batch_index "${batch_index} + 1")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
if(batch_files)
|
|
||||||
_json_test_add_unity_batch(${pool}${batch_index} ${cxx_standard} ${args_MAIN} ${is_private} ${batch_files})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endforeach()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# json_test_should_build_32bit_test(
|
# json_test_should_build_32bit_test(
|
||||||
# <build_32bit_var> <build_32bit_only_var> <input>)
|
# <build_32bit_var> <build_32bit_only_var> <input>)
|
||||||
|
|||||||
@@ -341,13 +341,14 @@ def download(url, docs) -> str:
|
|||||||
raise ValueError(f'not an http(s) URL: {url}')
|
raise ValueError(f'not an http(s) URL: {url}')
|
||||||
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
req = urllib.request.Request(u.geturl(), headers={'User-Agent': USER_AGENT})
|
||||||
# (the scheme is checked above)
|
# (the scheme is checked above)
|
||||||
|
# nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
||||||
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
with urllib.request.urlopen(req, timeout=20) as r: # nosec B310
|
||||||
data = r.read()
|
data = r.read()
|
||||||
ctype = r.headers.get_content_type()
|
ctype = r.headers.get_content_type()
|
||||||
path = urllib.parse.unquote(u.path).lstrip('/')
|
path = urllib.parse.unquote(u.path).lstrip('/')
|
||||||
ext = os.path.splitext(path)[1]
|
ext = os.path.splitext(path)[1]
|
||||||
if u.query or not ext or path.endswith('/'):
|
if u.query or not ext or path.endswith('/'):
|
||||||
digest = hashlib.sha1(url.encode(), usedforsecurity=False).hexdigest()[:12]
|
digest = hashlib.sha256(url.encode()).hexdigest()[:12]
|
||||||
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
path = os.path.join(os.path.dirname(path), digest + CONTENT_TYPE_EXT.get(ctype, ext or '.bin'))
|
||||||
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
rel = os.path.normpath(os.path.join('assets', 'external', u.hostname, path))
|
||||||
out = os.path.join(docs, rel)
|
out = os.path.join(docs, rel)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||||
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
- [**JSON_NO_AUTOMATIC_UDLS**](json_no_automatic_udls.md) - do not include the user-defined string literals (UDLs) automatically
|
||||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
|
- [**JSON_NO_SIMD**](json_no_simd.md) - use only portable code instead of vector instructions (SSE2, SSSE3, NEON)
|
||||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# JSON_NO_SIMD
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined, the library uses only portable C++ code where it would otherwise use vector instructions: when scanning
|
||||||
|
strings during parsing, and when writing floating-point numbers during serialization.
|
||||||
|
|
||||||
|
The macro is an escape hatch, for example for toolchains that cannot compile the vector code, for sanitizers or
|
||||||
|
analyzers that do not understand it, or to compare the speed of both variants. It does not change any result: values,
|
||||||
|
exceptions, and error positions are the same either way. Only the speed differs.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_NO_SIMD` is not defined, and vector instructions are used where the target has them.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_NO_SIMD
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
!!! note "Which instructions are used"
|
||||||
|
|
||||||
|
- **x86-64:** SSE2, which every x86-64 CPU has, finds the end of runs of plain string characters 16 bytes at a time
|
||||||
|
and writes the digits of floating-point numbers. The UTF-8 check of non-ASCII text needs SSSE3, which is not part
|
||||||
|
of x86-64: it is compiled for SSSE3 with a function attribute and used when the CPU has SSSE3 (every x86-64 CPU
|
||||||
|
since about 2011), else the portable check. Code compiled for SSSE3 anyway (e.g., with `-mssse3` or `-march=native`)
|
||||||
|
skips this check of the CPU. 32-bit x86 uses SSE2 for strings when the compiler targets it.
|
||||||
|
- **AArch64:** NEON, which every 64-bit Arm CPU has, checks UTF-8 text and long runs of plain string characters 16
|
||||||
|
bytes at a time, and writes the digits of floating-point numbers. With MSVC, only the floating-point digits use
|
||||||
|
NEON.
|
||||||
|
- **Other targets:** portable code only.
|
||||||
|
|
||||||
|
Strings are only scanned this way for contiguous input and for the characters a `#!cpp std::istream` holds in its
|
||||||
|
stream buffer (see [`JSON_DISABLE_STREAM_BULK_SCAN`](json_disable_stream_bulk_scan.md)); other inputs are read one
|
||||||
|
character at a time.
|
||||||
|
|
||||||
|
!!! note "Interaction with `JSON_USE_SIMDUTF`"
|
||||||
|
|
||||||
|
With [`JSON_USE_SIMDUTF`](json_use_simdutf.md), runs of string characters are first validated by simdutf; the code
|
||||||
|
described here is used for what simdutf does not accept, so that errors are reported at the same position.
|
||||||
|
`JSON_NO_SIMD` does not affect simdutf.
|
||||||
|
|
||||||
|
!!! note "ABI compatibility"
|
||||||
|
|
||||||
|
The macro selects between two definitions of the library's internal scanning and formatting functions. Whether it
|
||||||
|
is defined is encoded in the [namespace](../../features/namespace.md) (tag `_ns`), 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.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example "Example: switching off vector instructions"
|
||||||
|
|
||||||
|
The program parses and serializes the same values with and without the macro:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_NO_SIMD
|
||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
const json j = json::parse(R"({"name": "日本語 text", "pi": 3.141592653589793})");
|
||||||
|
std::cout << j.dump() << '\n'; // {"name":"日本語 text","pi":3.141592653589793}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||||
|
- [**dump**](../basic_json/dump.md) - serialization
|
||||||
|
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||||
|
- [**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.
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
||||||
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
||||||
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in validator (which uses SSSE3 or NEON where
|
||||||
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
available, see [`JSON_NO_SIMD`](json_no_simd.md)). On text with many non-ASCII characters (e.g. CJK or emoji) this can
|
||||||
|
validate faster, depending on the CPU.
|
||||||
|
|
||||||
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
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
|
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ the library.
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.11.0. Changed inline namespace name in version 3.11.2. Added the ABI tag `_dp` in
|
- 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`, `_ekmo`, `_dsbs`, `_dufio`, `_vns`, and
|
version 3.12.0, and the ABI tags `_bics`, `_psp`, `_snul`, `_sbu8`, `_ekmo`, `_dsbs`, `_dufio`, `_ns`, `_vns`,
|
||||||
`_vssse3` in version 3.13.0; see
|
and `_vssse3` in version 3.13.0; see
|
||||||
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
[`nlohmann` Namespace](../../features/namespace.md#structure).
|
||||||
@@ -174,6 +174,13 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
|||||||
|
|
||||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||||
|
|
||||||
|
## `JSON_NO_SIMD`
|
||||||
|
|
||||||
|
When defined, the library uses only portable code instead of vector instructions (SSE2, SSSE3, NEON) for scanning
|
||||||
|
strings and writing floating-point numbers. Results are the same either way. It is not defined by default.
|
||||||
|
|
||||||
|
See [full documentation of `JSON_NO_SIMD`](../api/macros/json_no_simd.md).
|
||||||
|
|
||||||
## `JSON_NO_THREAD_LOCAL`
|
## `JSON_NO_THREAD_LOCAL`
|
||||||
|
|
||||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ The complete default namespace name is derived as follows:
|
|||||||
`_dsbs`.
|
`_dsbs`.
|
||||||
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
- [`JSON_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md) defined non-zero appends
|
||||||
`_dufio`.
|
`_dufio`.
|
||||||
|
- [`JSON_NO_SIMD`](../api/macros/json_no_simd.md) defined appends `_ns`.
|
||||||
- [`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md) defined appends `_vns`.
|
- [`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`.
|
- [`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
|
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||||
@@ -114,6 +115,7 @@ follows:
|
|||||||
([`JSON_STRICT_BINARY_UTF8`](../api/macros/json_strict_binary_utf8.md)), `_ekmo`
|
([`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_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_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_DISABLE_UNLOCKED_FILE_IO`](../api/macros/json_disable_unlocked_file_io.md)), `_ns`
|
||||||
|
([`JSON_NO_SIMD`](../api/macros/json_no_simd.md)), `_vns`
|
||||||
([`JSON_VIEW_NO_SIMD`](../api/macros/json_view_no_simd.md)), and `_vssse3`
|
([`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.
|
([`JSON_VIEW_USE_SSSE3`](../api/macros/json_view_use_ssse3.md)) in version 3.13.0.
|
||||||
@@ -79,6 +79,10 @@ 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
|
[`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.
|
thread must use the same `#!cpp FILE` object while it is parsed.
|
||||||
|
|
||||||
|
Where the target has them, strings are scanned with vector instructions: SSE2 on x86-64 (and SSSE3 for UTF-8 text, when
|
||||||
|
the CPU has it) and NEON on AArch64. [`JSON_NO_SIMD`](../../api/macros/json_no_simd.md) selects portable code instead,
|
||||||
|
again without changing any result.
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|
||||||
The library offers two parsing models:
|
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
|
- it can reconstruct already-consumed input on demand for error messages, instead of copying every character as it is
|
||||||
read, and
|
read, and
|
||||||
- the lexer can scan ordinary string characters directly out of the buffer, several bytes at a time, rather than one
|
- the lexer can scan ordinary string characters, numbers, and indentation directly out of the buffer, several bytes at
|
||||||
character (and one function call) at a time.
|
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
|
A `#!cpp std::istream` (including `#!cpp std::ifstream`) gets the same bulk scanning: `input_stream_adapter` scans the
|
||||||
`input_stream_adapter` or `file_input_adapter`, which read one character (or one block, for binary formats) at a time
|
characters its stream buffer already holds (the *get area*) and asks the stream buffer for more only when they are used
|
||||||
and expose neither optimization -- the lexer falls back to the same byte-at-a-time path it uses for any
|
up. Nothing is read ahead, so the stream is left exactly where it would be after reading one character at a time, and
|
||||||
non-contiguous, general-purpose iterator range. An iterator pair over non-contiguous but random-access storage (e.g.
|
[`operator>>`](../api/operator_gtgt.md) can still read several values from one stream. A stream buffer without a get
|
||||||
`#!cpp std::deque<char>::iterator`) gets the first optimization but not the second, since the byte-scanning fast path
|
area is read one character at a time. A `#!cpp FILE*` is still read one character at a time, but `file_input_adapter`
|
||||||
additionally requires contiguous storage.
|
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
|
Inputs that are still read one character at a time: iterator ranges that are not contiguous (e.g.
|
||||||
`#!cpp std::string`, a `#!cpp std::vector<char>`, or a pointer range to `parse` over a `#!cpp std::istream`. For a
|
`#!cpp std::list<char>` or `#!cpp std::istreambuf_iterator<char>`), wide strings, and user-defined adapters. An
|
||||||
file, that means reading it into a string first and then parsing the string, rather than passing a
|
iterator pair over non-contiguous but random-access storage (e.g. `#!cpp std::deque<char>::iterator`) gets the first
|
||||||
`#!cpp std::ifstream` directly to `parse` -- the latter never benefits from either optimization:
|
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
|
```cpp
|
||||||
// gets the contiguous fast paths
|
// fast: the stream buffer's get area is scanned in bulk
|
||||||
std::ifstream f("example.json");
|
std::ifstream f("example.json");
|
||||||
std::string contents((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
json j = json::parse(f);
|
||||||
json j = json::parse(contents);
|
|
||||||
|
|
||||||
// does not: input_stream_adapter has no fast path
|
|
||||||
std::ifstream f2("example.json");
|
|
||||||
json j2 = json::parse(f2);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
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
|
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)
|
the built-in scalar validator; streaming inputs (files, `#!cpp std::istream`, wide strings, user-defined adapters)
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ below are relative to `include/nlohmann`.
|
|||||||
|-----------|----------|
|
|-----------|----------|
|
||||||
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
||||||
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
||||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) |
|
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp), [`detail/input/simd_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/simd_scan.hpp) |
|
||||||
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
||||||
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
||||||
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
||||||
@@ -339,6 +339,11 @@ The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`]
|
|||||||
- `output_stream_adapter` writes to a `std::ostream`.
|
- `output_stream_adapter` writes to a `std::ostream`.
|
||||||
- `output_string_adapter` appends to a string.
|
- `output_string_adapter` appends to a string.
|
||||||
|
|
||||||
|
The `binary_writer` does not call an adapter for every byte. The `to_*` functions that return a `std::vector` write
|
||||||
|
into the vector through a sink (`output_vector_sink`) that is not virtual. The functions that take an output adapter
|
||||||
|
write through a sink (`output_adapter_sink`) that collects the output in a buffer of one kibibyte and passes it to the
|
||||||
|
adapter in blocks, so that the adapter is called (and a stream locked) once per block instead of once per value.
|
||||||
|
|
||||||
## Value conversion
|
## Value conversion
|
||||||
|
|
||||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||||
|
|||||||
@@ -10,6 +10,29 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
|||||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||||
|
|
||||||
|
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||||
|
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||||
|
|
||||||
|
1. Check out the release tag of the library version you use.
|
||||||
|
2. List the ABI tags and the macros that enable them:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis NATVIS_ARGS="--list-tags"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Generate a Natvis file with the tags of your configuration, for example for `JSON_DIAGNOSTICS`,
|
||||||
|
`JSON_DIAGNOSTIC_POSITIONS`, and `JSON_VIEW_NO_SIMD`:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make natvis NATVIS_ARGS="--abi-tags _diag,_dp,_vns --output my_nlohmann_json.natvis"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||||
|
|
||||||
|
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
||||||
|
ABI tags, doubling with each further tag). See
|
||||||
|
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||||
|
|
||||||
Debug engines that wrap LLDB instead of the MSVC debug engine (for example, `codelldb` in VS Code) only have
|
Debug engines that wrap LLDB instead of the MSVC debug engine (for example, `codelldb` in VS Code) only have
|
||||||
partial/experimental Natvis support, and commonly fall back to showing raw internal fields even with the
|
partial/experimental Natvis support, and commonly fall back to showing raw internal fields even with the
|
||||||
`.natvis` file present. Switching to `cppvsdbg` where available, or checking your debug extension's own Natvis
|
`.natvis` file present. Switching to `cppvsdbg` where available, or checking your debug extension's own Natvis
|
||||||
|
|||||||
@@ -235,17 +235,6 @@ Build the unit tests against the [simdutf](https://github.com/simdutf/simdutf) U
|
|||||||
its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
|
its version is set by the cache variable `JSON_SIMDUTF_VERSION`. This option is `OFF` by default. Depends on
|
||||||
`JSON_BuildTests`.
|
`JSON_BuildTests`.
|
||||||
|
|
||||||
### `JSON_TestUnityBuild`
|
|
||||||
|
|
||||||
Build the unit tests in batches of several test files per executable to speed up compilation: the files of a batch are
|
|
||||||
compiled as one translation unit, so the template instantiations of the library are shared. Every test file still gets
|
|
||||||
its own CTest test (e.g., `test-foo_cpp11`), which runs only the test cases of that file from the shared executable.
|
|
||||||
This option is `ON` by default (and `OFF` with MinGW). Set it to `OFF` to get one executable per test file, e.g., when
|
|
||||||
debugging a single file. The number of files per executable is set by the cache variable `JSON_TestUnityBatchSize`
|
|
||||||
(default: `8`). Related test files (e.g., all binary formats) are grouped explicitly in `tests/CMakeLists.txt`; each
|
|
||||||
group is compiled as one executable per C++ standard regardless of the batch size, and only the remaining files are
|
|
||||||
batched by size. Depends on `JSON_BuildTests`.
|
|
||||||
|
|
||||||
### `JSON_Valgrind`
|
### `JSON_Valgrind`
|
||||||
|
|
||||||
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ nav:
|
|||||||
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
- 'JSON_NO_AUTOMATIC_UDLS': api/macros/json_no_automatic_udls.md
|
||||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||||
|
- 'JSON_NO_SIMD': api/macros/json_no_simd.md
|
||||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||||
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
||||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||||
|
|||||||
+12
-12
@@ -6,7 +6,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "check-mermaid",
|
"name": "check-mermaid",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@asamuzakjp/dom-selector": {
|
"node_modules/@asamuzakjp/dom-selector": {
|
||||||
"version": "9.2.4",
|
"version": "10.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-9.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-10.0.3.tgz",
|
||||||
"integrity": "sha512-YnVzxDxLaqL0t7Q4wfcgHZjg55Wm6y5EaO7Bhu3q9J7wq/8iw3PhsnWiOQhYekx4VexnbkgWpTT6n+QIV7kczw==",
|
"integrity": "sha512-3+777rEPsOOjoy5/sRmIzHnoRDaLpm3XWY2dmWn/N7f/XwDG0bcpnFaKL3X9FYJUbNwLmpcJC1bUYexJRxsFlw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bidi-js": "^1.1.0",
|
"bidi-js": "^1.1.0",
|
||||||
@@ -1202,13 +1202,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsdom": {
|
"node_modules/jsdom": {
|
||||||
"version": "30.1.2",
|
"version": "30.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-30.1.3.tgz",
|
||||||
"integrity": "sha512-0FFE/jE1rppmVfUrJUgxqXjcwolZYIGtAgj1pTussMhNZxIxi7W/PvfWaMNroGi2B36X1IKHbexpZ9DhkoOPiQ==",
|
"integrity": "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asamuzakjp/css-color": "^7.1.3",
|
"@asamuzakjp/css-color": "^7.1.3",
|
||||||
"@asamuzakjp/dom-selector": "^9.2.4",
|
"@asamuzakjp/dom-selector": "^10.0.3",
|
||||||
"@bramus/specificity": "^2.4.2",
|
"@bramus/specificity": "^2.4.2",
|
||||||
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
"@csstools/css-syntax-patches-for-csstree": "^1.1.15",
|
||||||
"@exodus/bytes": "^1.16.0",
|
"@exodus/bytes": "^1.16.0",
|
||||||
@@ -1221,7 +1221,7 @@
|
|||||||
"parse5": "^8.0.1",
|
"parse5": "^8.0.1",
|
||||||
"saxes": "^6.0.0",
|
"saxes": "^6.0.0",
|
||||||
"tough-cookie": "^6.0.2",
|
"tough-cookie": "^6.0.2",
|
||||||
"undici": "^8.11.2",
|
"undici": "^8.12.1",
|
||||||
"w3c-xmlserializer": "^6.0.0",
|
"w3c-xmlserializer": "^6.0.0",
|
||||||
"webidl-conversions": "^8.0.1",
|
"webidl-conversions": "^8.0.1",
|
||||||
"whatwg-mimetype": "^5.0.0",
|
"whatwg-mimetype": "^5.0.0",
|
||||||
@@ -1521,9 +1521,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "8.11.2",
|
"version": "8.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-8.11.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
|
||||||
"integrity": "sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==",
|
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.19.0"
|
"node": ">=22.19.0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
"description": "Validate the Mermaid diagrams of the documentation (see check_mermaid.mjs)",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsdom": "30.1.2",
|
"jsdom": "30.1.3",
|
||||||
"mermaid": "11.17.2"
|
"mermaid": "11.17.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +122,12 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||||
#else
|
#else
|
||||||
@@ -139,9 +145,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#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_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(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, m) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(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, m)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -155,6 +161,7 @@
|
|||||||
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_STREAM_BULK_SCAN, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,24 @@
|
|||||||
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
#include <intrin0.h> // __umulh, _umul128, _BitScanForward64, _BitScanReverse64
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <cstdlib> // _byteswap_ushort, _byteswap_ulong, _byteswap_uint64
|
||||||
|
#endif
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
#include <intrin.h> // _BitScanForward, _BitScanReverse
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
#include <nlohmann/detail/macro_scope.hpp> // JSON_HEDLEY_ALWAYS_INLINE, NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
#if defined(__cpp_lib_bit_cast) || defined(__cpp_lib_bitops) || defined(__cpp_lib_byteswap)
|
||||||
|
#include <bit> // bit_cast, byteswap, countl_zero, countr_zero
|
||||||
|
#endif
|
||||||
|
|
||||||
// Portable bit-level helpers for the number and string scanners. They use
|
// Portable bit-level helpers for the number and string scanners. They use
|
||||||
// compiler builtins or platform-specific intrinsics where available and plain
|
// compiler builtins or platform-specific intrinsics where available, the
|
||||||
// C++ otherwise, so they work regardless of byte order.
|
// C++20 <bit> functions next, and plain C++ otherwise, so they work regardless
|
||||||
|
// of byte order. (The builtins and intrinsics come first: MSVC's
|
||||||
|
// std::countl_zero checks for LZCNT at run time unless AVX2 is enabled.)
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -33,6 +46,16 @@ inline int count_leading_zeros(std::uint64_t x) noexcept
|
|||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanReverse64(&index, x);
|
_BitScanReverse64(&index, x);
|
||||||
return 63 - static_cast<int>(index);
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanReverse(&index, static_cast<unsigned long>(x >> 32u))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return 31 - static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanReverse(&index, static_cast<unsigned long>(x)); // NOLINT(runtime/int)
|
||||||
|
return 63 - static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countl_zero(x);
|
||||||
#else
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
@@ -56,6 +79,16 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|||||||
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan*64 takes
|
||||||
_BitScanForward64(&index, x);
|
_BitScanForward64(&index, x);
|
||||||
return static_cast<int>(index);
|
return static_cast<int>(index);
|
||||||
|
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_ARM))
|
||||||
|
unsigned long index = 0; // NOLINT(runtime/int): the type _BitScan* takes
|
||||||
|
if (_BitScanForward(&index, static_cast<unsigned long>(x))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
return static_cast<int>(index);
|
||||||
|
}
|
||||||
|
_BitScanForward(&index, static_cast<unsigned long>(x >> 32u)); // NOLINT(runtime/int)
|
||||||
|
return 32 + static_cast<int>(index);
|
||||||
|
#elif defined(__cpp_lib_bitops)
|
||||||
|
return std::countr_zero(x);
|
||||||
#else
|
#else
|
||||||
int n = 0;
|
int n = 0;
|
||||||
for (int shift = 32; shift != 0; shift >>= 1)
|
for (int shift = 32; shift != 0; shift >>= 1)
|
||||||
@@ -70,6 +103,67 @@ inline int count_trailing_zeros(std::uint64_t x) noexcept
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// the object representation of @a from as a @a To (std::bit_cast in C++20)
|
||||||
|
template<typename To, typename From>
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE To bit_cast(const From& from) noexcept
|
||||||
|
{
|
||||||
|
static_assert(sizeof(To) == sizeof(From), "bit_cast needs types of the same size");
|
||||||
|
#if defined(__cpp_lib_bit_cast)
|
||||||
|
return std::bit_cast<To>(from);
|
||||||
|
#else
|
||||||
|
To to {};
|
||||||
|
std::memcpy(&to, &from, sizeof(To));
|
||||||
|
return to;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23); compilers lower
|
||||||
|
/// it to a single bswap/rev/movbe
|
||||||
|
inline std::uint16_t byteswap(std::uint16_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap16(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ushort(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return static_cast<std::uint16_t>((x >> 8u) | (x << 8u));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint32_t byteswap(std::uint32_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap32(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_ulong(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
return ((x & 0x000000FFu) << 24u) | ((x & 0x0000FF00u) << 8u)
|
||||||
|
| ((x & 0x00FF0000u) >> 8u) | ((x & 0xFF000000u) >> 24u);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// x with its bytes in reverse order (std::byteswap in C++23)
|
||||||
|
inline std::uint64_t byteswap(std::uint64_t x) noexcept
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_bswap64(x);
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
return _byteswap_uint64(x);
|
||||||
|
#elif defined(__cpp_lib_byteswap)
|
||||||
|
return std::byteswap(x);
|
||||||
|
#else
|
||||||
|
x = ((x & 0x00000000FFFFFFFFull) << 32u) | ((x & 0xFFFFFFFF00000000ull) >> 32u);
|
||||||
|
x = ((x & 0x0000FFFF0000FFFFull) << 16u) | ((x & 0xFFFF0000FFFF0000ull) >> 16u);
|
||||||
|
x = ((x & 0x00FF00FF00FF00FFull) << 8u) | ((x & 0xFF00FF00FF00FF00ull) >> 8u);
|
||||||
|
return x;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/// the 128-bit product of two 64-bit numbers
|
/// the 128-bit product of two 64-bit numbers
|
||||||
struct uint128_parts
|
struct uint128_parts
|
||||||
{
|
{
|
||||||
@@ -107,8 +201,8 @@ inline uint128_parts full_multiplication(std::uint64_t a, std::uint64_t b) noexc
|
|||||||
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
/// targets; always inlined, as GCC otherwise calls it in the number loops)
|
||||||
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::uint64_t read_eight_bytes(const unsigned char* b) noexcept
|
||||||
{
|
{
|
||||||
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) || (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
// the byte order already matches (all MSVC targets are little-endian)
|
// the byte order already matches
|
||||||
std::uint64_t result = 0;
|
std::uint64_t result = 0;
|
||||||
std::memcpy(&result, b, sizeof(result));
|
std::memcpy(&result, b, sizeof(result));
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -69,12 +69,10 @@ void from_json(const BasicJsonType& j, std::optional<T>& opt)
|
|||||||
}
|
}
|
||||||
#endif // JSON_HAS_CPP_17
|
#endif // JSON_HAS_CPP_17
|
||||||
|
|
||||||
// overloads for basic_json template parameters
|
// read a number value of any of the three number types into val;
|
||||||
template < typename BasicJsonType, typename ArithmeticType,
|
// returns false (leaving val untouched) if j is not a number
|
||||||
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
template<typename BasicJsonType, typename ArithmeticType>
|
||||||
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
bool get_number_value(const BasicJsonType& j, ArithmeticType& val)
|
||||||
int > = 0 >
|
|
||||||
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|
||||||
{
|
{
|
||||||
switch (static_cast<value_t>(j))
|
switch (static_cast<value_t>(j))
|
||||||
{
|
{
|
||||||
@@ -85,17 +83,17 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|||||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||||
JSON_ASSERT(ptr != nullptr);
|
JSON_ASSERT(ptr != nullptr);
|
||||||
val = static_cast<ArithmeticType>(*ptr);
|
val = static_cast<ArithmeticType>(*ptr);
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
{
|
{
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
{
|
{
|
||||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
@@ -106,7 +104,20 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_type_must_be("number", j);
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overloads for basic_json template parameters
|
||||||
|
template < typename BasicJsonType, typename ArithmeticType,
|
||||||
|
enable_if_t < std::is_arithmetic<ArithmeticType>::value&&
|
||||||
|
!std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,
|
||||||
|
int > = 0 >
|
||||||
|
void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!get_number_value(j, val)))
|
||||||
|
{
|
||||||
|
throw_type_must_be("number", j);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,18 +267,18 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserve() is called through this pair (modeled on from_json_object_reserve)
|
// reserve() is called through this pair (used for arrays and objects alike)
|
||||||
// so from_json_array_impl below has a single body for both ConstructibleArrayType
|
// so from_json_array_impl and the object from_json have a single body for both
|
||||||
// that support reserve() and those that don't.
|
// containers that support reserve() and those that don't.
|
||||||
template<typename ConstructibleArrayType>
|
template<typename ConstructibleContainerType>
|
||||||
auto from_json_array_reserve(ConstructibleArrayType& arr, typename ConstructibleArrayType::size_type size, priority_tag<1> /*unused*/)
|
auto reserve_if_possible(ConstructibleContainerType& c, typename ConstructibleContainerType::size_type size, priority_tag<1> /*unused*/)
|
||||||
-> decltype(arr.reserve(size), void())
|
-> decltype(c.reserve(size), void())
|
||||||
{
|
{
|
||||||
arr.reserve(size);
|
c.reserve(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConstructibleArrayType>
|
template<typename ConstructibleContainerType>
|
||||||
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
void reserve_if_possible(ConstructibleContainerType& /*c*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// append through push_back() where available; containers without it (such as
|
// append through push_back() where available; containers without it (such as
|
||||||
@@ -297,7 +308,7 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
|
|||||||
{
|
{
|
||||||
ConstructibleArrayType ret;
|
ConstructibleArrayType ret;
|
||||||
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||||
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
|
reserve_if_possible(ret, inner_array->size(), priority_tag<1> {});
|
||||||
for (const auto& elem : *inner_array)
|
for (const auto& elem : *inner_array)
|
||||||
{
|
{
|
||||||
// get<BasicJsonType>() returns *this, this won't call a from_json
|
// get<BasicJsonType>() returns *this, this won't call a from_json
|
||||||
@@ -378,17 +389,6 @@ inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ConstructibleObjectType>
|
|
||||||
auto from_json_object_reserve(ConstructibleObjectType& obj, typename ConstructibleObjectType::size_type size, priority_tag<1> /*unused*/)
|
|
||||||
-> decltype(obj.reserve(size), void())
|
|
||||||
{
|
|
||||||
obj.reserve(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename ConstructibleObjectType>
|
|
||||||
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
|
||||||
{}
|
|
||||||
|
|
||||||
// the keys of the source are sorted for the usual object types, so hinting the
|
// the keys of the source are sorted for the usual object types, so hinting the
|
||||||
// end makes every insertion constant-time (for containers that order their keys
|
// end makes every insertion constant-time (for containers that order their keys
|
||||||
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
||||||
@@ -417,7 +417,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
|||||||
|
|
||||||
ConstructibleObjectType ret;
|
ConstructibleObjectType ret;
|
||||||
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();
|
||||||
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
reserve_if_possible(ret, inner_object->size(), priority_tag<1> {});
|
||||||
for (const auto& p : *inner_object)
|
for (const auto& p : *inner_object)
|
||||||
{
|
{
|
||||||
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
||||||
@@ -437,6 +437,10 @@ template < typename BasicJsonType, typename ArithmeticType,
|
|||||||
int > = 0 >
|
int > = 0 >
|
||||||
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||||
{
|
{
|
||||||
|
// the number cases repeat get_number_value() on purpose: calling it here
|
||||||
|
// changes the inlining of get<T>() in the std::array conversion, which then
|
||||||
|
// runs 2.4x slower (GetArray benchmark, #5891); unlike get_arithmetic_value(),
|
||||||
|
// a boolean is accepted
|
||||||
switch (static_cast<value_t>(j))
|
switch (static_cast<value_t>(j))
|
||||||
{
|
{
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
|
|||||||
@@ -18,17 +18,14 @@
|
|||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <type_traits> // conditional
|
#include <type_traits> // conditional
|
||||||
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
#include <cstdlib> // _byteswap_uint64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||||
// digits of a double at once
|
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
||||||
#if defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC))
|
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
#define JSON_DTOA_SSE2 1
|
#define JSON_DTOA_SSE2 1
|
||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#elif (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
#elif !defined(JSON_NO_SIMD) && (defined(__aarch64__) || defined(_M_ARM64)) && !defined(_M_ARM64EC) && !defined(__ARM_BIG_ENDIAN)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#define JSON_DTOA_SSE2 0
|
#define JSON_DTOA_SSE2 0
|
||||||
#define JSON_DTOA_NEON 1
|
#define JSON_DTOA_NEON 1
|
||||||
@@ -37,6 +34,7 @@
|
|||||||
#define JSON_DTOA_NEON 0
|
#define JSON_DTOA_NEON 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
#include <nlohmann/detail/conversions/zmij.hpp>
|
#include <nlohmann/detail/conversions/zmij.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
@@ -66,16 +64,6 @@ For a detailed description of the algorithm see:
|
|||||||
namespace dtoa_impl
|
namespace dtoa_impl
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename Target, typename Source>
|
|
||||||
Target reinterpret_bits(const Source source)
|
|
||||||
{
|
|
||||||
static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
|
|
||||||
|
|
||||||
Target target;
|
|
||||||
std::memcpy(&target, &source, sizeof(Source));
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct diyfp // f * 2^e
|
struct diyfp // f * 2^e
|
||||||
{
|
{
|
||||||
static constexpr int kPrecision = 64; // = q
|
static constexpr int kPrecision = 64; // = q
|
||||||
@@ -230,7 +218,7 @@ boundaries compute_boundaries(FloatType value)
|
|||||||
|
|
||||||
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;
|
||||||
|
|
||||||
const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));
|
const auto bits = static_cast<std::uint64_t>(bit_cast<bits_type>(value));
|
||||||
const std::uint64_t E = bits >> (kPrecision - 1);
|
const std::uint64_t E = bits >> (kPrecision - 1);
|
||||||
const std::uint64_t F = bits & (kHiddenBit - 1);
|
const std::uint64_t F = bits & (kHiddenBit - 1);
|
||||||
|
|
||||||
@@ -1084,13 +1072,10 @@ inline std::uint64_t eight_digit_bytes(std::uint64_t abcdefgh) noexcept
|
|||||||
/// the byte stores once this is inlined)
|
/// the byte stores once this is inlined)
|
||||||
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
inline void store_msb_first(char* p, std::uint64_t v) noexcept
|
||||||
{
|
{
|
||||||
#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
#if JSON_LITTLE_ENDIAN_TARGET
|
||||||
v = __builtin_bswap64(v);
|
v = byteswap(v);
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
#elif JSON_BIG_ENDIAN_TARGET
|
||||||
std::memcpy(p, &v, sizeof(v));
|
|
||||||
#elif defined(_MSC_VER) // (little-endian on all its targets)
|
|
||||||
v = _byteswap_uint64(v);
|
|
||||||
std::memcpy(p, &v, sizeof(v));
|
std::memcpy(p, &v, sizeof(v));
|
||||||
#else
|
#else
|
||||||
for (unsigned i = 0; i < 8; ++i)
|
for (unsigned i = 0; i < 8; ++i)
|
||||||
@@ -1462,8 +1447,7 @@ char* write_positive_zmij(char* first, const char* last, FloatType value)
|
|||||||
{
|
{
|
||||||
static_assert(is_binary64<FloatType>::value,
|
static_assert(is_binary64<FloatType>::value,
|
||||||
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
"internal error: the conversion of Zmij needs IEEE 754 binary64 numbers");
|
||||||
std::uint64_t bits = 0;
|
const auto bits = bit_cast<std::uint64_t>(value);
|
||||||
std::memcpy(&bits, &value, sizeof(bits));
|
|
||||||
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
const zmij::shortest_decimal d = zmij::to_shortest(bits);
|
||||||
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
if (JSON_HEDLEY_LIKELY(last - first >= 41))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ enum class exception_id : int
|
|||||||
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
|
invalid_string_or_size = 113, ///< a binary input contains an invalid string or size specification
|
||||||
bson_unsupported_type = 114, ///< a BSON record type is not supported
|
bson_unsupported_type = 114, ///< a BSON record type is not supported
|
||||||
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
|
invalid_high_precision_number = 115, ///< a UBJSON/BJData high-precision number cannot be parsed
|
||||||
|
invalid_document_image = 116, ///< json_document::load() rejects an image
|
||||||
// invalid_iterator
|
// invalid_iterator
|
||||||
iterators_incompatible = 201, ///< the iterators of a range belong to different values
|
iterators_incompatible = 201, ///< the iterators of a range belong to different values
|
||||||
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
|
iterator_from_other_value = 202, ///< an iterator does not belong to the value it is used with
|
||||||
@@ -100,6 +101,8 @@ enum class exception_id : int
|
|||||||
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
|
invalid_utf8 = 316, ///< dump() finds a string that is not valid UTF-8
|
||||||
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
|
type_not_serializable = 317, ///< a value cannot be serialized to the requested binary format
|
||||||
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
|
enum_key_duplicate = 318, ///< an enum-keyed map has two keys that serialize to the same string
|
||||||
|
document_binary_value = 319, ///< a binary value is stored in a json_document
|
||||||
|
document_image_unsupported = 320, ///< a json_document image cannot be saved or loaded (discarded document, big-endian target)
|
||||||
discarded_value_used = 321, ///< a discarded value is used
|
discarded_value_used = 321, ///< a discarded value is used
|
||||||
// out_of_range
|
// out_of_range
|
||||||
array_index_out_of_range = 401, ///< an array index is out of range
|
array_index_out_of_range = 401, ///< an array index is out of range
|
||||||
@@ -117,6 +120,7 @@ enum class exception_id : int
|
|||||||
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
|
patch_remove_parent_not_container = 413, ///< the parent of a JSON Patch 'remove' target is not a container
|
||||||
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
|
patch_move_into_child = 414, ///< a JSON Patch 'move' moves a value into one of its children
|
||||||
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
|
subtype_out_of_range = 415, ///< a binary subtype does not fit into one byte
|
||||||
|
document_too_large = 416, ///< a json_document input, its edit storage, or an image reaches the 32-bit (4 GiB) limit
|
||||||
// other_error
|
// other_error
|
||||||
internal_error = 500, ///< unreachable code was reached
|
internal_error = 500, ///< unreachable code was reached
|
||||||
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
|
patch_test_failed = 501, ///< a JSON Patch 'test' operation failed
|
||||||
@@ -146,6 +150,13 @@ class exception : public std::exception
|
|||||||
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
|
return concat("[json.exception.", ename, '.', std::to_string(id_), "] ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// build the message "[json.exception.<ename>.<id>] <diagnostics><what_arg>"
|
||||||
|
template<typename BasicJsonContext>
|
||||||
|
static std::string what_impl(const std::string& ename, int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
|
{
|
||||||
|
return concat(name(ename, id_), diagnostics(context), what_arg);
|
||||||
|
}
|
||||||
|
|
||||||
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
|
static std::string diagnostics(std::nullptr_t /*leaf_element*/)
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
@@ -158,48 +169,33 @@ class exception : public std::exception
|
|||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
|
for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)
|
||||||
{
|
{
|
||||||
switch (current->m_parent->type())
|
const auto* parent = current->m_parent;
|
||||||
|
if (parent->is_array())
|
||||||
{
|
{
|
||||||
case value_t::array:
|
for (std::size_t i = 0; i < parent->m_data.m_value.array->size(); ++i)
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)
|
if (&parent->m_data.m_value.array->operator[](i) == current)
|
||||||
{
|
{
|
||||||
if (¤t->m_parent->m_data.m_value.array->operator[](i) == current)
|
tokens.emplace_back(std::to_string(i));
|
||||||
{
|
break;
|
||||||
tokens.emplace_back(std::to_string(i));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::object:
|
|
||||||
{
|
|
||||||
for (const auto& element : *current->m_parent->m_data.m_value.object)
|
|
||||||
{
|
|
||||||
if (&element.second == current)
|
|
||||||
{
|
|
||||||
// data() is null-terminated, so a key containing
|
|
||||||
// a null byte is cut short here rather than
|
|
||||||
// truncating the whole message at what()
|
|
||||||
tokens.emplace_back(element.first.data());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null: // LCOV_EXCL_LINE
|
|
||||||
case value_t::string: // LCOV_EXCL_LINE
|
|
||||||
case value_t::boolean: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_integer: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_unsigned: // LCOV_EXCL_LINE
|
|
||||||
case value_t::number_float: // LCOV_EXCL_LINE
|
|
||||||
case value_t::binary: // LCOV_EXCL_LINE
|
|
||||||
case value_t::discarded: // LCOV_EXCL_LINE
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
break; // LCOV_EXCL_LINE
|
|
||||||
}
|
}
|
||||||
|
else if (parent->is_object())
|
||||||
|
{
|
||||||
|
for (const auto& element : *parent->m_data.m_value.object)
|
||||||
|
{
|
||||||
|
if (&element.second == current)
|
||||||
|
{
|
||||||
|
// data() is null-terminated, so a key containing
|
||||||
|
// a null byte is cut short here rather than
|
||||||
|
// truncating the whole message at what()
|
||||||
|
tokens.emplace_back(element.first.data());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// all other parent types (primitives) contribute no token
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens.empty())
|
if (tokens.empty())
|
||||||
@@ -315,7 +311,7 @@ class invalid_iterator : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("invalid_iterator", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("invalid_iterator", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +335,7 @@ class type_error : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("type_error", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("type_error", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,7 +358,7 @@ class out_of_range : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("out_of_range", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("out_of_range", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +381,7 @@ class other_error : public exception
|
|||||||
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>
|
||||||
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)
|
||||||
{
|
{
|
||||||
const std::string w = concat(exception::name("other_error", id_), exception::diagnostics(context), what_arg);
|
const std::string w = exception::what_impl("other_error", id_, what_arg, context);
|
||||||
return {id_, w.c_str()};
|
return {id_, w.c_str()};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -293,6 +293,40 @@ class binary_reader
|
|||||||
return is_object ? sax->end_object() : sax->end_array();
|
return is_object ? sax->end_object() : sax->end_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief close the innermost open container and report whether that
|
||||||
|
completed the value being read
|
||||||
|
|
||||||
|
The value begun by the driver is complete once its container is not
|
||||||
|
inside another one.
|
||||||
|
|
||||||
|
@param[out] value_complete whether no container is left open
|
||||||
|
@return whether the SAX parser accepted the end event
|
||||||
|
*/
|
||||||
|
bool leave_container_and_check(bool& value_complete)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value_complete = container_stack.empty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read an object key and pass it to the SAX parser
|
||||||
|
|
||||||
|
@tparam GetKey member function that reads the key into a string
|
||||||
|
@param[in,out] key string the key is read into; cleared first
|
||||||
|
@return whether reading the key and the SAX event succeeded
|
||||||
|
*/
|
||||||
|
template<bool (binary_reader::*GetKey)(string_t&)>
|
||||||
|
bool read_object_key(string_t& key)
|
||||||
|
{
|
||||||
|
key.clear();
|
||||||
|
return (this->*GetKey)(key) && sax->key(key);
|
||||||
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
@@ -1064,6 +1098,34 @@ class binary_reader
|
|||||||
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief report an object key that is not a string
|
||||||
|
|
||||||
|
@param[in] found description of what was found instead of a string key
|
||||||
|
@return false
|
||||||
|
*/
|
||||||
|
bool unsupported_key_type(const char* found) const
|
||||||
|
{
|
||||||
|
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read the length of a CBOR string or byte array and then its bytes
|
||||||
|
|
||||||
|
@tparam NumberType the type of the length field
|
||||||
|
@tparam ContainerType the destination container (string_t or binary_t)
|
||||||
|
@param[in] context further context information (for diagnostics)
|
||||||
|
@param[out] result container the bytes are appended to
|
||||||
|
|
||||||
|
@return whether reading completed
|
||||||
|
*/
|
||||||
|
template<typename NumberType, typename ContainerType>
|
||||||
|
bool get_cbor_sized_bytes(const char* context, ContainerType& result)
|
||||||
|
{
|
||||||
|
NumberType len{};
|
||||||
|
return get_number(len) && get_bytes(len, context, result);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a definite-length CBOR string
|
@brief reads a definite-length CBOR string
|
||||||
|
|
||||||
@@ -1114,26 +1176,22 @@ class binary_reader
|
|||||||
|
|
||||||
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
|
case 0x78: // UTF-8 string (one-byte uint8_t for n follows)
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_cbor_sized_bytes<std::uint8_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
|
case 0x79: // UTF-8 string (two-byte uint16_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_cbor_sized_bytes<std::uint16_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
|
case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_cbor_sized_bytes<std::uint32_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
|
case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint64_t len{};
|
return get_cbor_sized_bytes<std::uint64_t>("string", result);
|
||||||
return get_number(len) && get_string(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1268,7 +1326,7 @@ class binary_reader
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
return unsupported_key_type(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1321,30 +1379,22 @@ class binary_reader
|
|||||||
|
|
||||||
case 0x58: // Binary data (one-byte uint8_t for n follows)
|
case 0x58: // Binary data (one-byte uint8_t for n follows)
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_cbor_sized_bytes<std::uint8_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x59: // Binary data (two-byte uint16_t for n follow)
|
case 0x59: // Binary data (two-byte uint16_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_cbor_sized_bytes<std::uint16_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x5A: // Binary data (four-byte uint32_t for n follow)
|
case 0x5A: // Binary data (four-byte uint32_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_cbor_sized_bytes<std::uint32_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
|
case 0x5B: // Binary data (eight-byte uint64_t for n follow)
|
||||||
{
|
{
|
||||||
std::uint64_t len{};
|
return get_cbor_sized_bytes<std::uint64_t>("binary", result);
|
||||||
return get_number(len) &&
|
|
||||||
get_binary(len, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -1559,12 +1609,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1573,8 +1623,7 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_cbor_object_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_cbor_object_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2045,20 +2094,17 @@ class binary_reader
|
|||||||
|
|
||||||
case 0xD9: // str 8
|
case 0xD9: // str 8
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_string_with_length<std::uint8_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDA: // str 16
|
case 0xDA: // str 16
|
||||||
{
|
{
|
||||||
std::uint16_t len{};
|
return get_string_with_length<std::uint16_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0xDB: // str 32
|
case 0xDB: // str 32
|
||||||
{
|
{
|
||||||
std::uint32_t len{};
|
return get_string_with_length<std::uint32_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -2155,7 +2201,7 @@ class binary_reader
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
return unsupported_key_type(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -2300,12 +2346,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (container_stack.back().remaining == 0)
|
if (container_stack.back().remaining == 0)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2318,8 +2364,7 @@ class binary_reader
|
|||||||
if (is_object)
|
if (is_object)
|
||||||
{
|
{
|
||||||
get();
|
get();
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_msgpack_object_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_msgpack_object_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2503,8 +2548,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
case 'U':
|
case 'U':
|
||||||
{
|
{
|
||||||
std::uint8_t len{};
|
return get_string_with_length<std::uint8_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'i':
|
case 'i':
|
||||||
@@ -2537,8 +2581,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint16_t len{};
|
return get_string_with_length<std::uint16_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
@@ -2547,8 +2590,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint32_t len{};
|
return get_string_with_length<std::uint32_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -2557,8 +2599,7 @@ class binary_reader
|
|||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::uint64_t len{};
|
return get_string_with_length<std::uint64_t>(result, context);
|
||||||
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -3374,12 +3415,12 @@ class binary_reader
|
|||||||
|
|
||||||
if (at_end)
|
if (at_end)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!leave_container()))
|
bool value_complete = false;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!leave_container_and_check(value_complete)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// the value begun here is complete once its container is
|
if (value_complete)
|
||||||
if (container_stack.empty())
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3388,8 +3429,7 @@ class binary_reader
|
|||||||
|
|
||||||
if (top.is_object)
|
if (top.is_object)
|
||||||
{
|
{
|
||||||
key.clear();
|
if (JSON_HEDLEY_UNLIKELY(!read_object_key<&binary_reader::get_bon8_key>(key)))
|
||||||
if (JSON_HEDLEY_UNLIKELY(!get_bon8_key(key) || !sax->key(key)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -4129,6 +4169,22 @@ class binary_reader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief read a length of type @a NumberType, then a string of that many bytes
|
||||||
|
|
||||||
|
@tparam NumberType the type of the length field
|
||||||
|
@param[out] result string the bytes are appended to
|
||||||
|
@param[in] context further context information (for diagnostics)
|
||||||
|
|
||||||
|
@return whether reading and validating the string succeeded
|
||||||
|
*/
|
||||||
|
template<typename NumberType>
|
||||||
|
bool get_string_with_length(string_t& result, const char* context)
|
||||||
|
{
|
||||||
|
NumberType len{};
|
||||||
|
return get_number(len) && get_string(len, result) && check_string_utf8(result, context);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief create a byte array by reading bytes from the input
|
@brief create a byte array by reading bytes from the input
|
||||||
|
|
||||||
@@ -4338,37 +4394,7 @@ class binary_reader
|
|||||||
const std::string& context) const
|
const std::string& context) const
|
||||||
{
|
{
|
||||||
std::string error_msg = "syntax error while parsing ";
|
std::string error_msg = "syntax error while parsing ";
|
||||||
|
error_msg += input_format_name(input_format);
|
||||||
switch (input_format)
|
|
||||||
{
|
|
||||||
case input_format_t::cbor:
|
|
||||||
error_msg += "CBOR";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::msgpack:
|
|
||||||
error_msg += "MessagePack";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::ubjson:
|
|
||||||
error_msg += "UBJSON";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bson:
|
|
||||||
error_msg += "BSON";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bjdata:
|
|
||||||
error_msg += "BJData";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::bon8:
|
|
||||||
error_msg += "BON8";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case input_format_t::json: // LCOV_EXCL_LINE
|
|
||||||
default: // LCOV_EXCL_LINE
|
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
|
|
||||||
return concat(error_msg, ' ', context, ": ", detail);
|
return concat(error_msg, ' ', context, ": ", detail);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,18 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm> // count, find, min
|
#include <algorithm> // count, find, max, min
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstring> // strlen
|
#include <cstring> // strlen
|
||||||
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next, reverse_iterator
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <memory> // unique_ptr
|
||||||
#include <streambuf> // streambuf
|
#include <streambuf> // streambuf
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer
|
||||||
#include <utility> // pair, declval
|
#include <utility> // declval, move, pair
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
@@ -66,12 +67,118 @@ namespace detail
|
|||||||
/// the supported input formats
|
/// the supported input formats
|
||||||
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
enum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata, bon8 };
|
||||||
|
|
||||||
|
/// @return the name of a binary input format, as used in exception messages
|
||||||
|
inline const char* input_format_name(const input_format_t format) noexcept
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case input_format_t::cbor:
|
||||||
|
return "CBOR";
|
||||||
|
case input_format_t::msgpack:
|
||||||
|
return "MessagePack";
|
||||||
|
case input_format_t::ubjson:
|
||||||
|
return "UBJSON";
|
||||||
|
case input_format_t::bson:
|
||||||
|
return "BSON";
|
||||||
|
case input_format_t::bjdata:
|
||||||
|
return "BJData";
|
||||||
|
case input_format_t::bon8:
|
||||||
|
return "BON8";
|
||||||
|
case input_format_t::json: // LCOV_EXCL_LINE
|
||||||
|
default: // LCOV_EXCL_LINE
|
||||||
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||||
|
return ""; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return the message of the parse_error thrown or reported for an empty input
|
||||||
|
inline const char* empty_input_message() noexcept
|
||||||
|
{
|
||||||
|
return "attempting to parse an empty input; check that your input string or stream contains the expected JSON";
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// input adapters //
|
// input adapters //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
#ifndef JSON_NO_IO
|
#ifndef JSON_NO_IO
|
||||||
/*!
|
/*!
|
||||||
|
A growable array of characters for the part of a token that an input adapter
|
||||||
|
copied out of its window: unlike std::vector<char>::push_back(), appending a
|
||||||
|
character takes no reference to a local, and is a few instructions when there
|
||||||
|
is room.
|
||||||
|
*/
|
||||||
|
class token_carry_buffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
token_carry_buffer() = default;
|
||||||
|
token_carry_buffer(const token_carry_buffer&) = delete;
|
||||||
|
token_carry_buffer& operator=(const token_carry_buffer&) = delete;
|
||||||
|
token_carry_buffer(token_carry_buffer&& rhs) noexcept
|
||||||
|
: m_data(std::move(rhs.m_data)), m_end(rhs.m_end), m_capacity_end(rhs.m_capacity_end)
|
||||||
|
{
|
||||||
|
rhs.m_end = rhs.m_capacity_end = nullptr;
|
||||||
|
}
|
||||||
|
token_carry_buffer& operator=(token_carry_buffer&&) = delete;
|
||||||
|
~token_carry_buffer() = default;
|
||||||
|
|
||||||
|
void clear() noexcept
|
||||||
|
{
|
||||||
|
m_end = m_data.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(char c)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(m_end == m_capacity_end))
|
||||||
|
{
|
||||||
|
grow(1);
|
||||||
|
}
|
||||||
|
*m_end++ = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append(const char* first, const char* last)
|
||||||
|
{
|
||||||
|
const auto n = static_cast<std::size_t>(last - first);
|
||||||
|
const auto room = static_cast<std::size_t>(m_capacity_end - m_end);
|
||||||
|
if (n > room)
|
||||||
|
{
|
||||||
|
grow(n);
|
||||||
|
}
|
||||||
|
if (n != 0)
|
||||||
|
{
|
||||||
|
std::memcpy(m_end, first, n);
|
||||||
|
}
|
||||||
|
m_end += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// replace the contents of @a out
|
||||||
|
void copy_to(std::vector<char>& out) const
|
||||||
|
{
|
||||||
|
out.assign(m_data.get(), m_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// make room for @a extra more characters
|
||||||
|
JSON_HEDLEY_NEVER_INLINE void grow(std::size_t extra)
|
||||||
|
{
|
||||||
|
const auto size = static_cast<std::size_t>(m_end - m_data.get());
|
||||||
|
const auto capacity = static_cast<std::size_t>(m_capacity_end - m_data.get());
|
||||||
|
const std::size_t new_capacity = (std::max)(capacity < 32 ? static_cast<std::size_t>(32) : 2 * capacity, size + extra);
|
||||||
|
std::unique_ptr<char[]> data(new char[new_capacity]); // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
|
if (size != 0)
|
||||||
|
{
|
||||||
|
std::memcpy(data.get(), m_data.get(), size);
|
||||||
|
}
|
||||||
|
m_data = std::move(data);
|
||||||
|
m_end = m_data.get() + size;
|
||||||
|
m_capacity_end = m_data.get() + new_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<char[]> m_data{}; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,readability-redundant-member-init)
|
||||||
|
char* m_end = nullptr;
|
||||||
|
char* m_capacity_end = nullptr;
|
||||||
|
};
|
||||||
|
/*!
|
||||||
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
Input adapter for stdio file access. This adapter read only 1 byte and do not use any
|
||||||
buffer. This adapter is a very low level adapter.
|
buffer. This adapter is a very low level adapter.
|
||||||
*/
|
*/
|
||||||
@@ -209,6 +316,7 @@ class input_stream_adapter
|
|||||||
, token_segment(rhs.token_segment)
|
, token_segment(rhs.token_segment)
|
||||||
, current_in_get_area(rhs.current_in_get_area)
|
, current_in_get_area(rhs.current_in_get_area)
|
||||||
, tracking_token(rhs.tracking_token)
|
, tracking_token(rhs.tracking_token)
|
||||||
|
, no_get_area(rhs.no_get_area)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
rhs.is = nullptr;
|
rhs.is = nullptr;
|
||||||
@@ -295,37 +403,20 @@ class input_stream_adapter
|
|||||||
std::char_traits<char>::int_type get_character()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
// sbumpc() refills the get area if it is exhausted
|
// the character is in the get area: step over it, as sbumpc() does
|
||||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
const char* const next = streambuf_access::get_ptr(sb);
|
||||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
if (JSON_HEDLEY_LIKELY(next != streambuf_access::get_end(sb)))
|
||||||
{
|
|
||||||
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;
|
current_in_get_area = true;
|
||||||
|
streambuf_access::bump(sb, 1);
|
||||||
|
return std::char_traits<char>::to_int_type(*next);
|
||||||
|
}
|
||||||
|
if (no_get_area)
|
||||||
|
{
|
||||||
|
return get_character_no_get_area();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return res;
|
return get_character_slow();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -334,6 +425,18 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
#if JSON_PRECISE_STREAM_POSITION
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
commit_lookahead();
|
commit_lookahead();
|
||||||
|
#endif
|
||||||
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
|
// the elements are in the get area: copy them, as sgetn() does, but
|
||||||
|
// without the virtual call
|
||||||
|
const char* const first = streambuf_access::get_ptr(sb);
|
||||||
|
const auto available = static_cast<std::size_t>(streambuf_access::get_end(sb) - first);
|
||||||
|
if (JSON_HEDLEY_LIKELY(count * sizeof(T) <= available && count * sizeof(T) <= static_cast<std::size_t>((std::numeric_limits<int>::max)())))
|
||||||
|
{
|
||||||
|
std::memcpy(dest, first, count * sizeof(T));
|
||||||
|
streambuf_access::bump(sb, static_cast<int>(count * sizeof(T)));
|
||||||
|
return count * sizeof(T);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
@@ -421,7 +524,7 @@ class input_stream_adapter
|
|||||||
// last one if @a drop_last.
|
// last one if @a drop_last.
|
||||||
void copy_token(std::vector<char>& out, bool drop_last) const
|
void copy_token(std::vector<char>& out, bool drop_last) const
|
||||||
{
|
{
|
||||||
out = token_carry;
|
token_carry.copy_to(out);
|
||||||
if (token_segment != nullptr)
|
if (token_segment != nullptr)
|
||||||
{
|
{
|
||||||
out.insert(out.end(), token_segment, current_end());
|
out.insert(out.end(), token_segment, current_end());
|
||||||
@@ -434,6 +537,47 @@ class input_stream_adapter
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#if !JSON_PRECISE_STREAM_POSITION
|
||||||
|
// the get area is exhausted, or there is none
|
||||||
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
#endif
|
||||||
|
std::char_traits<char>::int_type get_character_slow()
|
||||||
|
{
|
||||||
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
|
// sbumpc() refills the get area if it is exhausted
|
||||||
|
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||||
|
{
|
||||||
|
save_token_segment();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto res = sb->sbumpc();
|
||||||
|
// set eof manually, as we don't use the istream interface.
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||||
|
{
|
||||||
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
|
current_in_get_area = false;
|
||||||
|
#endif
|
||||||
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
|
}
|
||||||
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
|
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||||
|
{
|
||||||
|
// the consumed character is at gptr() - 1, unless the streambuf
|
||||||
|
// has no get area and returned it from uflow()
|
||||||
|
const char* const next = streambuf_access::get_ptr(sb);
|
||||||
|
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_in_get_area = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
// Reads the protected get-area pointers of any std::streambuf. Naming the
|
||||||
// members through a derived class makes forming the member pointers
|
// members through a derived class makes forming the member pointers
|
||||||
@@ -457,6 +601,11 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
(s->*(&streambuf_access::gbump))(n);
|
(s->*(&streambuf_access::gbump))(n);
|
||||||
}
|
}
|
||||||
|
// sbumpc() for an exhausted get area
|
||||||
|
static std::streambuf::int_type next_character(std::streambuf* s)
|
||||||
|
{
|
||||||
|
return (s->*(&streambuf_access::uflow))();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// one past the character last returned by get_character(), if that is in
|
// one past the character last returned by get_character(), if that is in
|
||||||
@@ -477,16 +626,53 @@ class input_stream_adapter
|
|||||||
{
|
{
|
||||||
if (token_segment != nullptr)
|
if (token_segment != nullptr)
|
||||||
{
|
{
|
||||||
token_carry.insert(token_carry.end(), token_segment, streambuf_access::get_end(sb));
|
token_carry.append(token_segment, streambuf_access::get_end(sb));
|
||||||
token_segment = nullptr;
|
token_segment = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !JSON_PRECISE_STREAM_POSITION
|
||||||
|
// The streambuf has no get area (e.g., std::cin synchronized with stdio):
|
||||||
|
// every character comes from uflow(), and the token is kept by value.
|
||||||
|
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area()
|
||||||
|
{
|
||||||
|
// the get area is exhausted, so this is what sbumpc() does
|
||||||
|
const auto res = streambuf_access::next_character(sb);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof() || streambuf_access::get_end(sb) != nullptr))
|
||||||
|
{
|
||||||
|
return get_character_no_get_area_other(res);
|
||||||
|
}
|
||||||
|
// current_in_get_area is false: only the first character of a get
|
||||||
|
// area, or the end of the input, is read here
|
||||||
|
if (tracking_token)
|
||||||
|
{
|
||||||
|
token_carry.push_back(std::char_traits<char>::to_char_type(res));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @a res came from uflow() of a streambuf without a get area: it is the
|
||||||
|
// end of the input, or the streambuf has a get area now
|
||||||
|
JSON_HEDLEY_NEVER_INLINE std::char_traits<char>::int_type get_character_no_get_area_other(std::char_traits<char>::int_type res)
|
||||||
|
{
|
||||||
|
if (res == std::char_traits<char>::eof())
|
||||||
|
{
|
||||||
|
current_in_get_area = false;
|
||||||
|
is->clear(is->rdstate() | std::ios::eofbit);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// the consumed character is at gptr() - 1
|
||||||
|
after_refill(res, streambuf_access::get_ptr(sb) != streambuf_access::get_begin(sb));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// character @a c was just read into a new get area (@a in_get_area) or
|
// character @a c was just read into a new get area (@a in_get_area) or
|
||||||
// returned without one
|
// returned without one
|
||||||
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
void after_refill(std::char_traits<char>::int_type c, bool in_get_area)
|
||||||
{
|
{
|
||||||
current_in_get_area = in_get_area;
|
current_in_get_area = in_get_area;
|
||||||
|
no_get_area = !in_get_area;
|
||||||
if (!tracking_token)
|
if (!tracking_token)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -525,13 +711,16 @@ class input_stream_adapter
|
|||||||
#endif
|
#endif
|
||||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
/// the part of the current token that was in earlier get areas
|
/// the part of the current token that was in earlier get areas
|
||||||
std::vector<char> token_carry{}; // NOLINT(readability-redundant-member-init)
|
token_carry_buffer token_carry{}; // NOLINT(readability-redundant-member-init)
|
||||||
/// start of the current token's part in the get area, or nullptr
|
/// start of the current token's part in the get area, or nullptr
|
||||||
const char* token_segment = nullptr;
|
const char* token_segment = nullptr;
|
||||||
/// whether the character last returned by get_character() is in the get area
|
/// whether the character last returned by get_character() is in the get area
|
||||||
bool current_in_get_area = false;
|
bool current_in_get_area = false;
|
||||||
/// whether a token is tracked at all; the binary readers never start one
|
/// whether a token is tracked at all; the binary readers never start one
|
||||||
bool tracking_token = false;
|
bool tracking_token = false;
|
||||||
|
/// whether the streambuf had no get area when it was last asked for a
|
||||||
|
/// character
|
||||||
|
bool no_get_area = false;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
#endif // JSON_NO_IO
|
#endif // JSON_NO_IO
|
||||||
@@ -1109,7 +1298,7 @@ inline file_input_adapter input_adapter(std::FILE* file)
|
|||||||
{
|
{
|
||||||
if (file == nullptr)
|
if (file == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
return file_input_adapter(file);
|
return file_input_adapter(file);
|
||||||
}
|
}
|
||||||
@@ -1118,7 +1307,7 @@ inline input_stream_adapter input_adapter(std::istream& stream)
|
|||||||
{
|
{
|
||||||
if (stream.rdbuf() == nullptr)
|
if (stream.rdbuf() == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
return input_stream_adapter(stream);
|
return input_stream_adapter(stream);
|
||||||
}
|
}
|
||||||
@@ -1143,7 +1332,7 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
|||||||
{
|
{
|
||||||
if (b == nullptr)
|
if (b == nullptr)
|
||||||
{
|
{
|
||||||
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, "attempting to parse an empty input; check that your input string or stream contains the expected JSON", nullptr));
|
JSON_THROW(parse_error::create(exception_id::syntax_error, 0, empty_input_message(), nullptr));
|
||||||
}
|
}
|
||||||
auto length = std::strlen(reinterpret_cast<const char*>(b));
|
auto length = std::strlen(reinterpret_cast<const char*>(b));
|
||||||
const auto* ptr = reinterpret_cast<const char*>(b);
|
const auto* ptr = reinterpret_cast<const char*>(b);
|
||||||
|
|||||||
@@ -315,6 +315,52 @@ struct diagnostic_positions
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief error state shared by the DOM-building SAX parsers
|
||||||
|
|
||||||
|
Holds whether a parse error occurred and whether errors are thrown, and
|
||||||
|
implements the parse_error() and is_errored() members of the SAX interface for
|
||||||
|
json_sax_dom_parser and json_sax_dom_callback_parser.
|
||||||
|
*/
|
||||||
|
class json_sax_dom_error_state
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit json_sax_dom_error_state(const bool allow_exceptions_) noexcept
|
||||||
|
: allow_exceptions(allow_exceptions_)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// make class move-only
|
||||||
|
json_sax_dom_error_state(const json_sax_dom_error_state&) = delete;
|
||||||
|
json_sax_dom_error_state(json_sax_dom_error_state&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)
|
||||||
|
json_sax_dom_error_state& operator=(const json_sax_dom_error_state&) = delete;
|
||||||
|
json_sax_dom_error_state& operator=(json_sax_dom_error_state&&) = delete; // the const member prevents assignment
|
||||||
|
~json_sax_dom_error_state() = default;
|
||||||
|
|
||||||
|
template<class Exception>
|
||||||
|
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
||||||
|
const Exception& ex)
|
||||||
|
{
|
||||||
|
errored = true;
|
||||||
|
static_cast<void>(ex);
|
||||||
|
if (allow_exceptions)
|
||||||
|
{
|
||||||
|
JSON_THROW(ex);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_errored() const
|
||||||
|
{
|
||||||
|
return errored;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// whether a syntax error occurred
|
||||||
|
bool errored = false;
|
||||||
|
/// whether to throw exceptions in case of errors
|
||||||
|
const bool allow_exceptions = true;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief SAX implementation to create a JSON value from SAX events
|
@brief SAX implementation to create a JSON value from SAX events
|
||||||
|
|
||||||
@@ -329,7 +375,7 @@ constructor contains the parsed value.
|
|||||||
@tparam BasicJsonType the JSON type
|
@tparam BasicJsonType the JSON type
|
||||||
*/
|
*/
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
class json_sax_dom_parser
|
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
@@ -345,7 +391,7 @@ class json_sax_dom_parser
|
|||||||
@param[in] allow_exceptions_ whether parse errors yield exceptions
|
@param[in] allow_exceptions_ whether parse errors yield exceptions
|
||||||
*/
|
*/
|
||||||
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
|
explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)
|
||||||
: root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
|
: json_sax_dom_error_state(allow_exceptions_), root(r), m_lexer_ref(lexer_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// make class move-only
|
// make class move-only
|
||||||
@@ -418,8 +464,9 @@ class json_sax_dom_parser
|
|||||||
JSON_ASSERT(!ref_stack.empty());
|
JSON_ASSERT(!ref_stack.empty());
|
||||||
JSON_ASSERT(ref_stack.back()->is_object());
|
JSON_ASSERT(ref_stack.back()->is_object());
|
||||||
|
|
||||||
// add null at the given key and store the reference for later
|
// add null at the given key and store the reference for later; the
|
||||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val));
|
// key is moved, as the lexer starts the next token with reset()
|
||||||
|
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](std::move(val)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,24 +520,6 @@ class json_sax_dom_parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Exception>
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
|
||||||
const Exception& ex)
|
|
||||||
{
|
|
||||||
errored = true;
|
|
||||||
static_cast<void>(ex);
|
|
||||||
if (allow_exceptions)
|
|
||||||
{
|
|
||||||
JSON_THROW(ex);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool is_errored() const
|
|
||||||
{
|
|
||||||
return errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -544,16 +573,12 @@ class json_sax_dom_parser
|
|||||||
std::vector<BasicJsonType*> ref_stack {};
|
std::vector<BasicJsonType*> ref_stack {};
|
||||||
/// helper to hold the reference for the next object element
|
/// helper to hold the reference for the next object element
|
||||||
BasicJsonType* object_element = nullptr;
|
BasicJsonType* object_element = nullptr;
|
||||||
/// whether a syntax error occurred
|
|
||||||
bool errored = false;
|
|
||||||
/// whether to throw exceptions in case of errors
|
|
||||||
const bool allow_exceptions = true;
|
|
||||||
/// the lexer reference to obtain the current position
|
/// the lexer reference to obtain the current position
|
||||||
lexer_t* m_lexer_ref = nullptr;
|
lexer_t* m_lexer_ref = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename InputAdapterType>
|
template<typename BasicJsonType, typename InputAdapterType>
|
||||||
class json_sax_dom_callback_parser
|
class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
@@ -569,7 +594,7 @@ class json_sax_dom_callback_parser
|
|||||||
parser_callback_t cb,
|
parser_callback_t cb,
|
||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
lexer_t* lexer_ = nullptr)
|
lexer_t* lexer_ = nullptr)
|
||||||
: root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)
|
: json_sax_dom_error_state(allow_exceptions_), root(r), callback(std::move(cb)), m_lexer_ref(lexer_)
|
||||||
{
|
{
|
||||||
keep_stack.push_back(true);
|
keep_stack.push_back(true);
|
||||||
}
|
}
|
||||||
@@ -836,24 +861,6 @@ class json_sax_dom_callback_parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Exception>
|
|
||||||
bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,
|
|
||||||
const Exception& ex)
|
|
||||||
{
|
|
||||||
errored = true;
|
|
||||||
static_cast<void>(ex);
|
|
||||||
if (allow_exceptions)
|
|
||||||
{
|
|
||||||
JSON_THROW(ex);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool is_errored() const
|
|
||||||
{
|
|
||||||
return errored;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// if there is a pending duplicate-key stash entry for this exact slot,
|
/// if there is a pending duplicate-key stash entry for this exact slot,
|
||||||
@@ -1095,12 +1102,8 @@ class json_sax_dom_callback_parser
|
|||||||
/// value is later rejected by the callback, instead of erasing the
|
/// value is later rejected by the callback, instead of erasing the
|
||||||
/// member entirely
|
/// member entirely
|
||||||
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||||
/// whether a syntax error occurred
|
|
||||||
bool errored = false;
|
|
||||||
/// callback function
|
/// callback function
|
||||||
const parser_callback_t callback = nullptr;
|
const parser_callback_t callback = nullptr;
|
||||||
/// whether to throw exceptions in case of errors
|
|
||||||
const bool allow_exceptions = true;
|
|
||||||
/// a discarded value for the callback
|
/// a discarded value for the callback
|
||||||
BasicJsonType discarded = BasicJsonType::value_t::discarded;
|
BasicJsonType discarded = BasicJsonType::value_t::discarded;
|
||||||
/// the lexer reference to obtain the current position
|
/// the lexer reference to obtain the current position
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cstring> // memcmp
|
||||||
#include <initializer_list> // initializer_list
|
#include <initializer_list> // initializer_list
|
||||||
#include <string> // char_traits, string
|
#include <string> // char_traits, string
|
||||||
#include <utility> // move
|
#include <utility> // move
|
||||||
@@ -417,30 +418,43 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||||
/// the first byte that needs individual handling (the closing quote, an
|
/// read the first byte that needs individual handling (the closing quote, an
|
||||||
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||||
void scan_string_bulk(std::true_type /*bulk*/)
|
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||||
|
/// Apple Clang merged this read with the other get() calls of the scan
|
||||||
|
/// function, which load the read position again right after it was stored
|
||||||
|
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||||
|
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
// a pending unget must be consumed through the normal path first
|
// a pending unget must be consumed through the normal path first
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const std::size_t remaining = ia.bulk_remaining();
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
if (remaining == 0)
|
if (remaining == 0)
|
||||||
{
|
{
|
||||||
return;
|
return get();
|
||||||
}
|
}
|
||||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
const auto* const raw = ia.bulk_data();
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(raw);
|
||||||
|
|
||||||
const std::size_t pos = string_bulk_run(data, remaining);
|
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||||
if (pos == 0)
|
if (pos != 0)
|
||||||
{
|
{
|
||||||
return;
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
|
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||||
}
|
}
|
||||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
if (pos == remaining)
|
||||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
{
|
||||||
|
return get(); // end of the contiguous input
|
||||||
|
}
|
||||||
|
// what get() does, with the byte at hand
|
||||||
|
advance_position();
|
||||||
|
ia.bulk_skip(1);
|
||||||
|
current = char_traits<char_type>::to_int_type(raw[pos]);
|
||||||
|
return track_after_read();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
/// consume @a n characters at @a data (the adapter's bulk_data()) and do
|
||||||
@@ -473,7 +487,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// streaming input: no bulk fast path
|
/// streaming input: no bulk fast path
|
||||||
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return get();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
@@ -501,11 +518,8 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// bulk-consume ordinary characters from contiguous input, then
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
// handle the next special byte through the switch below
|
// get the next character, the one that ends the run
|
||||||
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||||
|
|
||||||
// get the next character
|
|
||||||
switch (get())
|
|
||||||
{
|
{
|
||||||
// end of file while parsing the string
|
// end of file while parsing the string
|
||||||
case char_traits<char_type>::eof():
|
case char_traits<char_type>::eof():
|
||||||
@@ -642,194 +656,74 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
// invalid control characters
|
// invalid control characters
|
||||||
case 0x00:
|
case 0x00:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0000 (NUL) must be escaped to \\u0000";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x01:
|
case 0x01:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0001 (SOH) must be escaped to \\u0001";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x02:
|
case 0x02:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0002 (STX) must be escaped to \\u0002";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x03:
|
case 0x03:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0003 (ETX) must be escaped to \\u0003";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x04:
|
case 0x04:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0004 (EOT) must be escaped to \\u0004";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x05:
|
case 0x05:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0005 (ENQ) must be escaped to \\u0005";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x06:
|
case 0x06:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0006 (ACK) must be escaped to \\u0006";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x07:
|
case 0x07:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0007 (BEL) must be escaped to \\u0007";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x08:
|
case 0x08:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x09:
|
case 0x09:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0A:
|
case 0x0A:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0B:
|
case 0x0B:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000B (VT) must be escaped to \\u000B";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0C:
|
case 0x0C:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0D:
|
case 0x0D:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0E:
|
case 0x0E:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000E (SO) must be escaped to \\u000E";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x0F:
|
case 0x0F:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+000F (SI) must be escaped to \\u000F";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x10:
|
case 0x10:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0010 (DLE) must be escaped to \\u0010";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x11:
|
case 0x11:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0011 (DC1) must be escaped to \\u0011";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x12:
|
case 0x12:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0012 (DC2) must be escaped to \\u0012";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x13:
|
case 0x13:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0013 (DC3) must be escaped to \\u0013";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x14:
|
case 0x14:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0014 (DC4) must be escaped to \\u0014";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x15:
|
case 0x15:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0015 (NAK) must be escaped to \\u0015";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x16:
|
case 0x16:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0016 (SYN) must be escaped to \\u0016";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x17:
|
case 0x17:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0017 (ETB) must be escaped to \\u0017";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x18:
|
case 0x18:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0018 (CAN) must be escaped to \\u0018";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x19:
|
case 0x19:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+0019 (EM) must be escaped to \\u0019";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1A:
|
case 0x1A:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001A (SUB) must be escaped to \\u001A";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1B:
|
case 0x1B:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001B (ESC) must be escaped to \\u001B";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1C:
|
case 0x1C:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001C (FS) must be escaped to \\u001C";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1D:
|
case 0x1D:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001D (GS) must be escaped to \\u001D";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1E:
|
case 0x1E:
|
||||||
{
|
|
||||||
error_message = "invalid string: control character U+001E (RS) must be escaped to \\u001E";
|
|
||||||
return token_type::parse_error;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 0x1F:
|
case 0x1F:
|
||||||
{
|
{
|
||||||
error_message = "invalid string: control character U+001F (US) must be escaped to \\u001F";
|
static const std::array<const char*, 32> control_character_messages = {{
|
||||||
|
"invalid string: control character U+0000 (NUL) must be escaped to \\u0000",
|
||||||
|
"invalid string: control character U+0001 (SOH) must be escaped to \\u0001",
|
||||||
|
"invalid string: control character U+0002 (STX) must be escaped to \\u0002",
|
||||||
|
"invalid string: control character U+0003 (ETX) must be escaped to \\u0003",
|
||||||
|
"invalid string: control character U+0004 (EOT) must be escaped to \\u0004",
|
||||||
|
"invalid string: control character U+0005 (ENQ) must be escaped to \\u0005",
|
||||||
|
"invalid string: control character U+0006 (ACK) must be escaped to \\u0006",
|
||||||
|
"invalid string: control character U+0007 (BEL) must be escaped to \\u0007",
|
||||||
|
"invalid string: control character U+0008 (BS) must be escaped to \\u0008 or \\b",
|
||||||
|
"invalid string: control character U+0009 (HT) must be escaped to \\u0009 or \\t",
|
||||||
|
"invalid string: control character U+000A (LF) must be escaped to \\u000A or \\n",
|
||||||
|
"invalid string: control character U+000B (VT) must be escaped to \\u000B",
|
||||||
|
"invalid string: control character U+000C (FF) must be escaped to \\u000C or \\f",
|
||||||
|
"invalid string: control character U+000D (CR) must be escaped to \\u000D or \\r",
|
||||||
|
"invalid string: control character U+000E (SO) must be escaped to \\u000E",
|
||||||
|
"invalid string: control character U+000F (SI) must be escaped to \\u000F",
|
||||||
|
"invalid string: control character U+0010 (DLE) must be escaped to \\u0010",
|
||||||
|
"invalid string: control character U+0011 (DC1) must be escaped to \\u0011",
|
||||||
|
"invalid string: control character U+0012 (DC2) must be escaped to \\u0012",
|
||||||
|
"invalid string: control character U+0013 (DC3) must be escaped to \\u0013",
|
||||||
|
"invalid string: control character U+0014 (DC4) must be escaped to \\u0014",
|
||||||
|
"invalid string: control character U+0015 (NAK) must be escaped to \\u0015",
|
||||||
|
"invalid string: control character U+0016 (SYN) must be escaped to \\u0016",
|
||||||
|
"invalid string: control character U+0017 (ETB) must be escaped to \\u0017",
|
||||||
|
"invalid string: control character U+0018 (CAN) must be escaped to \\u0018",
|
||||||
|
"invalid string: control character U+0019 (EM) must be escaped to \\u0019",
|
||||||
|
"invalid string: control character U+001A (SUB) must be escaped to \\u001A",
|
||||||
|
"invalid string: control character U+001B (ESC) must be escaped to \\u001B",
|
||||||
|
"invalid string: control character U+001C (FS) must be escaped to \\u001C",
|
||||||
|
"invalid string: control character U+001D (GS) must be escaped to \\u001D",
|
||||||
|
"invalid string: control character U+001E (RS) must be escaped to \\u001E",
|
||||||
|
"invalid string: control character U+001F (US) must be escaped to \\u001F"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
error_message = control_character_messages[static_cast<std::size_t>(current)];
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1625,9 +1519,12 @@ scan_number_done:
|
|||||||
const std::size_t avail = rem + 1;
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
// 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 i = 0;
|
||||||
std::size_t dot_index = std::string::npos;
|
std::size_t dot_index = std::string::npos;
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
std::uint64_t w = 0;
|
||||||
if (data[0] == '-')
|
if (data[0] == '-')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_integer;
|
number_type = token_type::value_integer;
|
||||||
@@ -1637,22 +1534,21 @@ scan_number_done:
|
|||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const std::size_t integer_begin = i;
|
||||||
if (data[i] == '0')
|
if (data[i] == '0')
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
else if (data[i] >= '1' && data[i] <= '9')
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
++i;
|
i = accumulate_digits(data, i, avail, w);
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
|
std::size_t digit_count = i - integer_begin;
|
||||||
|
std::size_t fraction_digits = 0;
|
||||||
if (i < avail && data[i] == '.')
|
if (i < avail && data[i] == '.')
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
@@ -1662,29 +1558,39 @@ scan_number_done:
|
|||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
return token_type::uninitialized;
|
||||||
}
|
}
|
||||||
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
const std::size_t fraction_begin = i;
|
||||||
{
|
i = accumulate_digits(data, i, avail, w);
|
||||||
++i;
|
fraction_digits = i - fraction_begin;
|
||||||
}
|
digit_count += fraction_digits;
|
||||||
}
|
}
|
||||||
// the mantissa ends here, whether or not an exponent part follows
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
const std::size_t mantissa_end = i;
|
const std::size_t mantissa_end = i;
|
||||||
|
std::int64_t exponent = 0;
|
||||||
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
{
|
{
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
++i;
|
++i;
|
||||||
|
bool negative_exponent = false;
|
||||||
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
{
|
{
|
||||||
|
negative_exponent = data[i] == '-';
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
{
|
{
|
||||||
return token_type::uninitialized;
|
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')
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
{
|
{
|
||||||
|
if (exponent < saturation)
|
||||||
|
{
|
||||||
|
exponent = (exponent * 10) + (data[i] - '0');
|
||||||
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
exponent = negative_exponent ? -exponent : exponent;
|
||||||
}
|
}
|
||||||
const std::size_t len = i;
|
const std::size_t len = i;
|
||||||
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
if (number_may_continue(len == avail, std::integral_constant<bool, bulk_window> {}))
|
||||||
@@ -1705,7 +1611,10 @@ scan_number_done:
|
|||||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
if (number_type != token_type::value_float)
|
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))
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
{
|
{
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
@@ -1719,15 +1628,42 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 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;
|
decimal_point_position = dot_index;
|
||||||
|
|
||||||
bulk_consume(data + 1, len - 1);
|
bulk_consume(data + 1, len - 1);
|
||||||
read_number_terminator(std::integral_constant<bool, bulk_window> {});
|
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);
|
||||||
return convert_number(number_type, mantissa_end);
|
return convert_number(number_type, mantissa_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1756,6 +1692,35 @@ scan_number_done:
|
|||||||
/// the input is not handed back, so the terminator can stay unread
|
/// the input is not handed back, so the terminator can stay unread
|
||||||
void read_number_terminator(std::false_type /*window*/) const noexcept {}
|
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
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
{
|
{
|
||||||
@@ -1770,18 +1735,23 @@ scan_number_done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] rest the literal text after its first character, which
|
||||||
@param[in] length the length of the passed literal text
|
is current (its length is a template parameter, so
|
||||||
@param[in] return_type the token type to return on success
|
that the comparison of the contiguous fast path is
|
||||||
|
a fixed-size load, not a call of memcmp)
|
||||||
|
@param[in] return_type the token type to return on success
|
||||||
*/
|
*/
|
||||||
JSON_HEDLEY_NON_NULL(2)
|
template<std::size_t N>
|
||||||
token_type scan_literal(const char_type* literal_text, const std::size_t length,
|
JSON_HEDLEY_NEVER_INLINE token_type scan_literal(const char (&rest)[N], token_type return_type) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||||
token_type return_type)
|
|
||||||
{
|
{
|
||||||
JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);
|
constexpr std::size_t length = N - 1;
|
||||||
for (std::size_t i = 1; i < length; ++i)
|
if (scan_literal_bulk(rest, length, std::integral_constant<bool, bulk_scan> {}))
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))
|
return return_type;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != static_cast<char_type>(rest[i])))
|
||||||
{
|
{
|
||||||
error_message = "invalid literal";
|
error_message = "invalid literal";
|
||||||
return token_type::parse_error;
|
return token_type::parse_error;
|
||||||
@@ -1790,6 +1760,27 @@ scan_number_done:
|
|||||||
return return_type;
|
return return_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// contiguous input: compare the rest of a literal at once; false (and
|
||||||
|
/// nothing consumed) if it does not match, so that the byte path reports
|
||||||
|
/// the error at the right character
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
if (next_unget || ia.bulk_remaining() < length
|
||||||
|
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bulk_consume(reinterpret_cast<const char*>(ia.bulk_data()), length);
|
||||||
|
current = static_cast<char_int_type>(static_cast<unsigned char>(rest[length - 1]));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: no bulk fast path
|
||||||
|
bool scan_literal_bulk(const char* /*rest*/, const std::size_t /*length*/, std::false_type /*bulk*/) const noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////
|
/////////////////////
|
||||||
// input management
|
// input management
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -1798,6 +1789,7 @@ scan_number_done:
|
|||||||
void reset() noexcept
|
void reset() noexcept
|
||||||
{
|
{
|
||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
|
pending_number_text = nullptr;
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -2057,10 +2049,30 @@ scan_number_done:
|
|||||||
/// return current string value
|
/// return current string value
|
||||||
string_t& get_string()
|
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)
|
// a number token holds '.' regardless of the locale (#4084)
|
||||||
return token_buffer;
|
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
|
// diagnostics
|
||||||
/////////////////////
|
/////////////////////
|
||||||
@@ -2394,21 +2406,14 @@ scan_number_done:
|
|||||||
return token_type::value_separator;
|
return token_type::value_separator;
|
||||||
|
|
||||||
// literals
|
// literals
|
||||||
|
// (the text after the first character; no local arrays, which
|
||||||
|
// would give this function a stack canary on every token)
|
||||||
case 't':
|
case 't':
|
||||||
{
|
return scan_literal("rue", token_type::literal_true);
|
||||||
std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};
|
|
||||||
return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);
|
|
||||||
}
|
|
||||||
case 'f':
|
case 'f':
|
||||||
{
|
return scan_literal("alse", token_type::literal_false);
|
||||||
std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};
|
|
||||||
return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);
|
|
||||||
}
|
|
||||||
case 'n':
|
case 'n':
|
||||||
{
|
return scan_literal("ull", token_type::literal_null);
|
||||||
std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};
|
|
||||||
return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// string
|
// string
|
||||||
case '\"':
|
case '\"':
|
||||||
@@ -2456,6 +2461,11 @@ scan_number_done:
|
|||||||
/// whether the next get() call should just return current
|
/// whether the next get() call should just return current
|
||||||
bool next_unget = false;
|
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
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // int64_t, uint64_t
|
#include <cstdint> // int64_t, uint64_t
|
||||||
#include <cstdlib> // strtof, strtod, strtold
|
#include <cstdlib> // strtof, strtod, strtold
|
||||||
#include <cstring> // memcpy
|
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||||
@@ -275,13 +274,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);
|
return static_cast<std::uint32_t>(((v & 0x0000FFFF0000FFFFu) * 42949672960001u) >> 32u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// whether [first, last) contains a digit other than '0'
|
/// whether the eight bytes in @a v (see read_eight_bytes()) are all ASCII
|
||||||
inline bool has_nonzero_digit(const char* first, const char* last) noexcept
|
/// 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),
|
/// the value of the validated exponent digits [+-]?[0-9]+ in [first, last),
|
||||||
@@ -327,7 +390,7 @@ truncated is set.
|
|||||||
std::string::npos if there is none
|
std::string::npos if there is none
|
||||||
@param[in] mantissa_end index of the 'e'/'E', or the token length
|
@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
|
std::size_t decimal_point_position, std::size_t mantissa_end) noexcept
|
||||||
{
|
{
|
||||||
float_significand s;
|
float_significand s;
|
||||||
@@ -342,16 +405,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
|||||||
int remaining = 19; // digits that still fit into w
|
int remaining = 19; // digits that still fit into w
|
||||||
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
if (*p != '0') // the integer part is "0" or [1-9][0-9]*
|
||||||
{
|
{
|
||||||
while (remaining >= 8 && integer_last - p >= 8)
|
p = accumulate_significant_digits(p, integer_last, remaining, w);
|
||||||
{
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
s.exponent = integer_last - p;
|
s.exponent = integer_last - p;
|
||||||
s.truncated = has_nonzero_digit(p, integer_last);
|
s.truncated = has_nonzero_digit(p, integer_last);
|
||||||
}
|
}
|
||||||
@@ -370,16 +424,7 @@ inline float_significand parse_float_significand(const char* first, const char*
|
|||||||
s.exponent -= p - zeros;
|
s.exponent -= p - zeros;
|
||||||
}
|
}
|
||||||
const char* const digits = p;
|
const char* const digits = p;
|
||||||
while (remaining >= 8 && mantissa_last - p >= 8)
|
p = accumulate_significant_digits(p, mantissa_last, remaining, w);
|
||||||
{
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
s.exponent -= p - digits;
|
s.exponent -= p - digits;
|
||||||
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
s.truncated = s.truncated || has_nonzero_digit(p, mantissa_last);
|
||||||
}
|
}
|
||||||
@@ -712,15 +757,14 @@ std::uint64_t digit_comparison(const char* first, const char* last, std::uint64_
|
|||||||
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, double& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||||
std::memcpy(&value, &bits, sizeof(value));
|
value = bit_cast<double>(bits);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
/// the float with the IEEE-754 bits @a bits (the lower 32)
|
||||||
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
||||||
{
|
{
|
||||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||||
const auto bits32 = static_cast<std::uint32_t>(bits);
|
value = bit_cast<float>(static_cast<std::uint32_t>(bits));
|
||||||
std::memcpy(&value, &bits32, sizeof(bits32));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the powers of ten that are exact in binary64 (up to 10^22)
|
/// the powers of ten that are exact in binary64 (up to 10^22)
|
||||||
|
|||||||
@@ -51,6 +51,20 @@ template<typename BasicJsonType>
|
|||||||
using parser_callback_t =
|
using parser_callback_t =
|
||||||
std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;
|
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
|
@brief syntax analysis
|
||||||
|
|
||||||
@@ -151,26 +165,63 @@ class parser
|
|||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
if (strict)
|
return finish_input(*sax, strict);
|
||||||
{
|
|
||||||
// strict mode: next byte must be EOF
|
|
||||||
if (get_token() != token_type::end_of_input)
|
|
||||||
{
|
|
||||||
return syntax_error(*sax, exception_message(token_type::end_of_input, "value"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the caller keeps using the input: position it right after
|
|
||||||
// the value by leaving the character that terminated it
|
|
||||||
m_lexer.release_lookahead();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 apply the end-of-input handling after a value was parsed
|
||||||
|
|
||||||
|
In strict mode, the next token must be the end of the input; otherwise
|
||||||
|
parse_error.101 is reported through @a sax. In non-strict mode, the
|
||||||
|
lookahead is released so the caller can keep reading the input right after
|
||||||
|
the parsed value.
|
||||||
|
|
||||||
|
@param[in,out] sax the SAX parser to report an error to
|
||||||
|
@param[in] strict whether to expect the last token to be EOF
|
||||||
|
@return true, or what @a sax returned for the error
|
||||||
|
*/
|
||||||
|
template<typename SAX>
|
||||||
|
bool finish_input(SAX& sax, const bool strict)
|
||||||
|
{
|
||||||
|
if (strict)
|
||||||
|
{
|
||||||
|
// strict mode: next byte must be EOF
|
||||||
|
if (get_token() != token_type::end_of_input)
|
||||||
|
{
|
||||||
|
return syntax_error(sax, exception_message(token_type::end_of_input, "value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the caller keeps using the input: position it right after
|
||||||
|
// the value by leaving the character that terminated it
|
||||||
|
m_lexer.release_lookahead();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief run a DOM SAX parser to completion and position the lexer
|
@brief run a DOM SAX parser to completion and position the lexer
|
||||||
|
|
||||||
@@ -190,20 +241,7 @@ class parser
|
|||||||
{
|
{
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
if (strict)
|
static_cast<void>(finish_input(sdp, strict));
|
||||||
{
|
|
||||||
// in strict mode, input must be completely read
|
|
||||||
if (get_token() != token_type::end_of_input)
|
|
||||||
{
|
|
||||||
syntax_error(sdp, exception_message(token_type::end_of_input, "value"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// the caller keeps using the input: position it right after
|
|
||||||
// the value by leaving the character that terminated it
|
|
||||||
m_lexer.release_lookahead();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !sdp.is_errored();
|
return !sdp.is_errored();
|
||||||
}
|
}
|
||||||
@@ -213,8 +251,9 @@ class parser
|
|||||||
bool sax_parse_internal(SAX* sax)
|
bool sax_parse_internal(SAX* sax)
|
||||||
{
|
{
|
||||||
// stack to remember the hierarchy of structured values we are parsing
|
// stack to remember the hierarchy of structured values we are parsing
|
||||||
// true = array; false = object
|
// true = array; false = object (bytes, not std::vector<bool>, whose
|
||||||
std::vector<bool> states;
|
// push_back() is a call with GCC)
|
||||||
|
std::vector<std::uint8_t> states;
|
||||||
// value to avoid a goto (see comment where set to true)
|
// value to avoid a goto (see comment where set to true)
|
||||||
bool skip_to_state_evaluation = false;
|
bool skip_to_state_evaluation = false;
|
||||||
|
|
||||||
@@ -247,7 +286,7 @@ class parser
|
|||||||
{
|
{
|
||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -259,7 +298,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an object
|
// remember we are now inside an object
|
||||||
states.push_back(false);
|
states.push_back(0);
|
||||||
|
|
||||||
// parse values
|
// parse values
|
||||||
get_token();
|
get_token();
|
||||||
@@ -284,7 +323,7 @@ class parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
// remember we are now inside an array
|
// remember we are now inside an array
|
||||||
states.push_back(true);
|
states.push_back(1);
|
||||||
|
|
||||||
// parse values (no need to call get_token)
|
// parse values (no need to call get_token)
|
||||||
continue;
|
continue;
|
||||||
@@ -301,7 +340,7 @@ class parser
|
|||||||
out_of_range::create(exception_id::number_overflow, concat("number overflow parsing '", m_lexer.get_token_string(), '\''), nullptr));
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -347,7 +386,7 @@ class parser
|
|||||||
|
|
||||||
case token_type::value_string:
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -372,7 +411,7 @@ class parser
|
|||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
if (JSON_HEDLEY_UNLIKELY(m_lexer.get_chars_read_total() == 1))
|
||||||
{
|
{
|
||||||
return syntax_error(*sax, "attempting to parse an empty input; check that your input string or stream contains the expected JSON");
|
return syntax_error(*sax, empty_input_message());
|
||||||
}
|
}
|
||||||
|
|
||||||
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
return syntax_error(*sax, exception_message(token_type::literal_or_value, "value"));
|
||||||
@@ -401,7 +440,7 @@ class parser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (states.back()) // array
|
if (states.back() != 0) // array
|
||||||
{
|
{
|
||||||
// comma -> next value
|
// comma -> next value
|
||||||
// or end of array (ignore_trailing_commas = true)
|
// or end of array (ignore_trailing_commas = true)
|
||||||
@@ -456,7 +495,7 @@ class parser
|
|||||||
return syntax_error(*sax, exception_message(token_type::value_string, "object key"));
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,321 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-FileCopyrightText: 2018-2025 The simdjson authors <https://github.com/simdjson/simdjson>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <atomic> // atomic
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t, uint64_t
|
||||||
|
|
||||||
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// Vector code for runs of string bytes, used by the lexer for contiguous input
|
||||||
|
// (see string_bulk_run()). NEON (AArch64) and SSE2 (x86-64) belong to the
|
||||||
|
// baseline instruction sets and are used by default. The vector UTF-8 check
|
||||||
|
// needs NEON or SSSE3. SSSE3 is not part of x86-64, and the code must not
|
||||||
|
// depend on the flags of a translation unit (two translation units with
|
||||||
|
// different flags would have different definitions of the same inline
|
||||||
|
// functions): the check is compiled for SSSE3 with a function attribute and
|
||||||
|
// used where the CPU has SSSE3 (all x86-64 CPUs since about 2011), else the
|
||||||
|
// portable check. Code compiled for SSSE3 anyway (-mssse3 or more) skips the
|
||||||
|
// CPU check. JSON_NO_SIMD selects the portable code everywhere.
|
||||||
|
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
||||||
|
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
||||||
|
// without disabling the SSE2 code.
|
||||||
|
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
#include <arm_neon.h>
|
||||||
|
#define JSON_SCAN_NEON 1
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_NEON 0
|
||||||
|
#endif
|
||||||
|
// (x86 only: other targets can define __SSE2__ as well, e.g., WebAssembly with -msse2, but have no <cpuid.h>)
|
||||||
|
#if !defined(JSON_NO_SIMD) && !JSON_SCAN_NEON && (defined(__x86_64__) || defined(__i386__) || defined(_M_X64) || defined(_M_IX86)) && !defined(_M_ARM64EC) && (defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2))
|
||||||
|
#include <emmintrin.h>
|
||||||
|
#define JSON_SCAN_SSE2 1
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSE2 0
|
||||||
|
#endif
|
||||||
|
#if JSON_SCAN_SSE2 && defined(__SSSE3__)
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSSE3 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#endif
|
||||||
|
#if JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && !defined(NLOHMANN_JSON_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) && !defined(__cpp_modules)))
|
||||||
|
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3; GCC with C++20
|
||||||
|
// modules drops the target attribute from functions it reads from a module
|
||||||
|
// interface and then rejects the intrinsics: "needs isa option -mssse3")
|
||||||
|
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||||
|
// json_view.hpp both include it
|
||||||
|
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||||
|
#elif JSON_SCAN_SSE2 && !JSON_SCAN_SSSE3 && defined(_MSC_VER)
|
||||||
|
// (MSVC compiles intrinsics of any instruction set)
|
||||||
|
#include <intrin.h>
|
||||||
|
#include <tmmintrin.h>
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET
|
||||||
|
#else
|
||||||
|
#define JSON_SCAN_SSSE3_DISPATCH 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_SCAN_SSSE3_TARGET
|
||||||
|
#endif
|
||||||
|
#define JSON_SCAN_VECTOR (JSON_SCAN_NEON || JSON_SCAN_SSE2)
|
||||||
|
#define JSON_SCAN_VECTOR_UTF8 (JSON_SCAN_NEON || JSON_SCAN_SSSE3 || JSON_SCAN_SSSE3_DISPATCH)
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/*!
|
||||||
|
@brief the first byte of a string run that is a quote, a backslash, a control
|
||||||
|
character, or not ASCII, 16 bytes per step
|
||||||
|
|
||||||
|
Stops at such a byte, or where fewer than 16 bytes are left (the caller tells
|
||||||
|
the two apart). A signed compare with 0x20 finds control characters and
|
||||||
|
non-ASCII bytes at once.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* vector_plain_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
while (e - p >= 16)
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x16_t in = vld1q_u8(p);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))),
|
||||||
|
vcltq_s8(vreinterpretq_s8_u8(in), vdupq_n_s8(0x20)));
|
||||||
|
// one nibble per byte (the usual NEON replacement of x86's movemask, see
|
||||||
|
// D. Kutenin, "Porting x86 vector bitmask optimizations to Arm NEON", 2022)
|
||||||
|
const std::uint64_t bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + (count_trailing_zeros(bits) >> 2u);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||||
|
_mm_cmplt_epi8(in, _mm_set1_epi8(0x20)));
|
||||||
|
const auto bits = static_cast<std::uint64_t>(static_cast<unsigned>(_mm_movemask_epi8(special)));
|
||||||
|
if (bits != 0)
|
||||||
|
{
|
||||||
|
return p + count_trailing_zeros(bits);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
p += 16;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/// vector_plain_run() for runs that are already longer than a few words:
|
||||||
|
/// inline with SSE2, out of line with NEON, where a larger string loop slows
|
||||||
|
/// down the rest of the lexer
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
JSON_HEDLEY_NEVER_INLINE inline
|
||||||
|
#else
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
#endif
|
||||||
|
const unsigned char* vector_plain_run_long(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
return vector_plain_run(p, e);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
/// whether the CPU has SSSE3 (CPUID leaf 1, ECX bit 9)
|
||||||
|
inline bool cpu_ssse3() noexcept
|
||||||
|
{
|
||||||
|
#if defined(_MSC_VER) && !defined(__clang__)
|
||||||
|
std::array<int, 4> regs {{}};
|
||||||
|
__cpuid(regs.data(), 1);
|
||||||
|
return (static_cast<unsigned>(regs[2]) & (1u << 9u)) != 0;
|
||||||
|
#else
|
||||||
|
unsigned eax = 0;
|
||||||
|
unsigned ebx = 0;
|
||||||
|
unsigned ecx = 0;
|
||||||
|
unsigned edx = 0;
|
||||||
|
return __get_cpuid(1, &eax, &ebx, &ecx, &edx) != 0 && (ecx & (1u << 9u)) != 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether the CPU has SSSE3, asked once: the answer is kept in an atomic
|
||||||
|
/// that is initialized at compile time, so that neither a guard of a local
|
||||||
|
/// static nor a global constructor is needed (threads that ask at the same
|
||||||
|
/// time all store the same answer)
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE bool cpu_has_ssse3() noexcept
|
||||||
|
{
|
||||||
|
static std::atomic<int> known{0}; // 0: not asked yet, 1: no, 2: yes
|
||||||
|
int state = known.load(std::memory_order_relaxed);
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(state == 0))
|
||||||
|
{
|
||||||
|
state = cpu_ssse3() ? 2 : 1;
|
||||||
|
known.store(state, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
return state == 2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
|
/// Tables of the UTF-8 check of J. Keiser and D. Lemire, "Validating UTF-8 In
|
||||||
|
/// Less Than One Instruction Per Byte" (2021), as in simdjson ("lookup4"): each
|
||||||
|
/// maps a nibble (high and low nibble of the previous byte, high nibble of the
|
||||||
|
/// current byte) to the errors it allows; a byte pair is ill-formed if all
|
||||||
|
/// three have an error bit in common.
|
||||||
|
template<typename Dummy = void>
|
||||||
|
struct utf8_lookup4
|
||||||
|
{
|
||||||
|
static constexpr std::uint8_t too_short = 1u << 0u, too_long = 1u << 1u, overlong_3 = 1u << 2u, too_large = 1u << 3u;
|
||||||
|
static constexpr std::uint8_t surrogate = 1u << 4u, overlong_2 = 1u << 5u, too_large_1000 = 1u << 6u, overlong_4 = 1u << 6u;
|
||||||
|
static constexpr std::uint8_t two_conts = 1u << 7u, carry = too_short | too_long | two_conts;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_high;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_1_low;
|
||||||
|
static const std::array<std::uint8_t, 16> byte_2_high;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_long, too_long, too_long, too_long, too_long, too_long, too_long, too_long,
|
||||||
|
two_conts, two_conts, two_conts, two_conts,
|
||||||
|
too_short | overlong_2, too_short, too_short | overlong_3 | surrogate, too_short | too_large | too_large_1000 | overlong_4
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_1_low =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
carry | overlong_3 | overlong_2 | overlong_4, carry | overlong_2, carry, carry,
|
||||||
|
carry | too_large, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000, carry | too_large | too_large_1000,
|
||||||
|
carry | too_large | too_large_1000, carry | too_large | too_large_1000 | surrogate, carry | too_large | too_large_1000, carry | too_large | too_large_1000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Dummy>
|
||||||
|
const std::array<std::uint8_t, 16> utf8_lookup4<Dummy>::byte_2_high =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
too_short, too_short, too_short, too_short, too_short, too_short, too_short, too_short,
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large_1000 | overlong_4),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | overlong_3 | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
static_cast<std::uint8_t>(too_long | overlong_2 | two_conts | surrogate | too_large),
|
||||||
|
too_short, too_short, too_short, too_short
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the run of plain string bytes and well-formed UTF-8 at p (a character
|
||||||
|
boundary), 16 bytes per step
|
||||||
|
|
||||||
|
The first quote, backslash, or control character is found with vector
|
||||||
|
compares, and the UTF-8 check of J. Keiser and D. Lemire covers the bytes up
|
||||||
|
to it. Returns true with @a block at that byte; returns false with @a block at
|
||||||
|
the start of the 16 bytes where the check found ill-formed UTF-8 or where
|
||||||
|
fewer than 16 bytes are left, for the caller to finish one sequence at a time.
|
||||||
|
Out of line, so that no constants of the check occupy registers in the
|
||||||
|
caller's loop. On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
|
||||||
|
*/
|
||||||
|
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(const unsigned char* p, const unsigned char* e, const unsigned char*& block_out) noexcept
|
||||||
|
{
|
||||||
|
using lookup = utf8_lookup4<>;
|
||||||
|
const unsigned char* block = p;
|
||||||
|
#if JSON_SCAN_NEON
|
||||||
|
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
||||||
|
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||||
|
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||||
|
uint8x16_t prev = vdupq_n_u8(0);
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const uint8x16_t in = vld1q_u8(block);
|
||||||
|
const uint8x16_t special = vorrq_u8(vorrq_u8(vceqq_u8(in, vdupq_n_u8('"')), vceqq_u8(in, vdupq_n_u8('\\'))), vcltq_u8(in, vdupq_n_u8(0x20)));
|
||||||
|
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||||
|
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||||
|
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||||
|
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||||
|
const std::uint64_t special_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(special), 4)), 0);
|
||||||
|
const std::uint64_t err_bits = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(vtstq_u8(err, err)), 4)), 0);
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
// errors up to the special byte count (an incomplete sequence
|
||||||
|
// before a quote shows at the quote); the bytes after it do not
|
||||||
|
const unsigned k = static_cast<unsigned>(count_trailing_zeros(special_bits)) >> 2u;
|
||||||
|
const std::uint64_t upto = k == 15 ? ~std::uint64_t{0} :
|
||||||
|
(std::uint64_t{1} << (4u * (k + 1u))) - 1u;
|
||||||
|
if ((err_bits & upto) == 0)
|
||||||
|
{
|
||||||
|
block_out = block + k;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// the same with SSSE3 (pshufb for the table lookups; nibbles from 16-bit
|
||||||
|
// shifts, as there are no byte shifts)
|
||||||
|
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||||
|
const __m128i zero = _mm_setzero_si128();
|
||||||
|
__m128i prev = zero;
|
||||||
|
while (e - block >= 16)
|
||||||
|
{
|
||||||
|
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||||
|
const __m128i special = _mm_or_si128(_mm_or_si128(_mm_cmpeq_epi8(in, _mm_set1_epi8('"')), _mm_cmpeq_epi8(in, _mm_set1_epi8('\\'))),
|
||||||
|
_mm_cmpeq_epi8(_mm_subs_epu8(in, _mm_set1_epi8(0x1F)), zero)); // in < 0x20
|
||||||
|
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||||
|
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||||
|
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||||
|
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||||
|
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||||
|
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||||
|
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||||
|
const auto special_bits = static_cast<unsigned>(_mm_movemask_epi8(special));
|
||||||
|
const auto err_bits = ~static_cast<unsigned>(_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero))) & 0xFFFFu;
|
||||||
|
if (special_bits != 0)
|
||||||
|
{
|
||||||
|
const auto k = static_cast<unsigned>(count_trailing_zeros(static_cast<std::uint64_t>(special_bits)));
|
||||||
|
if ((err_bits & ((2u << k) - 1u)) == 0)
|
||||||
|
{
|
||||||
|
block_out = block + k;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (err_bits != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev = in;
|
||||||
|
block += 16;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
block_out = block;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
#include <nlohmann/detail/bit_ops.hpp>
|
#include <nlohmann/detail/bit_ops.hpp>
|
||||||
|
#include <nlohmann/detail/input/simd_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||||
@@ -34,7 +35,7 @@
|
|||||||
// This file contains the byte-level string-scanning helpers used by the lexer's
|
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||||
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||||
// lexer's template parameters) so they are free functions, keeping the lexer
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||||
// itself focused on the state machine; see lexer::scan_string_bulk().
|
// itself focused on the state machine; see lexer::get_after_string_run().
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -66,7 +67,7 @@ inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
|||||||
|
|
||||||
// return the index of the first is_string_special() byte in [data, data+n), or
|
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||||
// n if every byte is ordinary; scans 8 bytes at a time
|
// n if every byte is ordinary; scans 8 bytes at a time
|
||||||
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
for (; i + 8 <= n; i += 8)
|
for (; i + 8 <= n; i += 8)
|
||||||
@@ -95,7 +96,7 @@ inline std::size_t find_string_special(const unsigned char* data, std::size_t n)
|
|||||||
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
||||||
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
||||||
// \u007f under ensure_ascii).
|
// \u007f under ensure_ascii).
|
||||||
inline bool is_ascii_copyable(unsigned char c) noexcept
|
constexpr bool is_ascii_copyable(unsigned char c) noexcept
|
||||||
{
|
{
|
||||||
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
||||||
}
|
}
|
||||||
@@ -235,35 +236,143 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar (C++11) computation of the bulk run length: the number of leading
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
/// the end of scan_string_vector from block, where the vector loop stopped
|
||||||
|
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
||||||
|
/// time, from the start of a sequence that crosses into the block
|
||||||
|
inline const unsigned char* scan_string_finish(const unsigned char* p, const unsigned char* block, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
for (int i = 1; i <= 3 && block - i >= p; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char c = block[-i];
|
||||||
|
if (c < 0x80)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (c >= 0xC0)
|
||||||
|
{
|
||||||
|
const int len = 2 + static_cast<int>(c >= 0xE0) + static_cast<int>(c >= 0xF0);
|
||||||
|
if (len > i)
|
||||||
|
{
|
||||||
|
block -= i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (p = block; p != e;)
|
||||||
|
{
|
||||||
|
if (*p < 0x80)
|
||||||
|
{
|
||||||
|
if (is_string_special(*p))
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
++p;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t n = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
p += n;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the rest of a string from p (a character boundary), with the vector UTF-8
|
||||||
|
/// check and the scalar check for the end of the block where it stopped
|
||||||
|
inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
const unsigned char* block = p;
|
||||||
|
if (vector_utf8_run(p, e, block))
|
||||||
|
{
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
return scan_string_finish(p, block, e);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_SCAN_VECTOR
|
||||||
|
/// the end of the run of plain string characters at @a p: the first quote,
|
||||||
|
/// backslash, control character, or non-ASCII byte, or @a e. The first words
|
||||||
|
/// are checked with SWAR (one; four with NEON, where the vector mask costs
|
||||||
|
/// more), as most keys and short values end there; longer runs continue 16
|
||||||
|
/// bytes at a time.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE const unsigned char* plain_run_end(const unsigned char* p, const unsigned char* e) noexcept
|
||||||
|
{
|
||||||
|
constexpr int prefix_words = JSON_SCAN_NEON ? 4 : 1;
|
||||||
|
for (int words = prefix_words; words > 0 && e - p >= 8; --words)
|
||||||
|
{
|
||||||
|
const std::uint64_t special = swar_string_special(read_eight_bytes(p));
|
||||||
|
if (special != 0)
|
||||||
|
{
|
||||||
|
return p + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||||
|
}
|
||||||
|
p += 8;
|
||||||
|
}
|
||||||
|
// the vector loop only for 16 bytes or more: the serializer passes a
|
||||||
|
// string without its closing quote, so most of its runs end at e, and a
|
||||||
|
// call of the out-of-line NEON loop for a shorter rest made dump() up to
|
||||||
|
// 10% slower
|
||||||
|
if (e - p >= 16)
|
||||||
|
{
|
||||||
|
p = vector_plain_run_long(p, e);
|
||||||
|
if (e - p >= 16)
|
||||||
|
{
|
||||||
|
return p; // the vector loop stopped at a special byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p + find_string_special(p, static_cast<std::size_t>(e - p));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Computation of the bulk run length: the number of leading bytes in
|
||||||
|
// [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||||
// sequences, stopping before the first byte that needs individual handling (the
|
// sequences, stopping before the first byte that needs individual handling (the
|
||||||
// closing quote, an escape, a control character, or an ill-formed/truncated
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||||
// sequence). ASCII is skipped 8 bytes at a time.
|
// sequence). ASCII is skipped 16 bytes at a time with SSE2 and 8 bytes at a
|
||||||
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
// time otherwise; runs of non-ASCII text use the vector UTF-8 check where
|
||||||
|
// available.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
std::size_t pos = 0;
|
const unsigned char* const e = data + n;
|
||||||
while (pos < n)
|
const unsigned char* p = data;
|
||||||
|
while (p != e)
|
||||||
{
|
{
|
||||||
pos += find_string_special(data + pos, n - pos);
|
#if JSON_SCAN_VECTOR
|
||||||
if (pos >= n || data[pos] < 0x80u)
|
p = plain_run_end(p, e);
|
||||||
|
#else
|
||||||
|
p += find_string_special(p, static_cast<std::size_t>(e - p));
|
||||||
|
#endif
|
||||||
|
if (p == e || *p < 0x80u)
|
||||||
{
|
{
|
||||||
break; // end of buffer, or a quote/escape/control byte
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
}
|
}
|
||||||
|
#if JSON_SCAN_VECTOR_UTF8
|
||||||
|
#if JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
if (JSON_HEDLEY_LIKELY(cpu_has_ssse3()))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(scan_string_vector(p, e) - data);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if !JSON_SCAN_VECTOR_UTF8 || JSON_SCAN_SSSE3_DISPATCH
|
||||||
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
// a run of multi-byte sequences (e.g. CJK text) is validated sequence
|
||||||
// by sequence without searching for the next special byte in between
|
// by sequence without searching for the next special byte in between
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
const std::size_t seq = validate_one_utf8(p, static_cast<std::size_t>(e - p));
|
||||||
if (seq == 0)
|
if (seq == 0)
|
||||||
{
|
{
|
||||||
return pos; // ill-formed or truncated: let the byte path diagnose it
|
return static_cast<std::size_t>(p - data); // ill-formed or truncated: let the byte path diagnose it
|
||||||
}
|
}
|
||||||
pos += seq;
|
p += seq;
|
||||||
}
|
}
|
||||||
while (pos < n && data[pos] >= 0x80u);
|
while (p != e && *p >= 0x80u);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return pos;
|
return static_cast<std::size_t>(p - data);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
@@ -304,7 +413,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t
|
|||||||
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
||||||
// scalar helper recomputes the exact valid prefix so the byte path still
|
// scalar helper recomputes the exact valid prefix so the byte path still
|
||||||
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
||||||
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
{
|
{
|
||||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
const std::size_t run = find_string_delimiter(data, n);
|
const std::size_t run = find_string_delimiter(data, n);
|
||||||
@@ -316,6 +425,42 @@ inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noe
|
|||||||
return scalar_string_bulk_run(data, n);
|
return scalar_string_bulk_run(data, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||||
|
/// scalar_string_bulk_run() from @a start on, out of line (see
|
||||||
|
/// lexer_string_bulk_run())
|
||||||
|
JSON_HEDLEY_NEVER_INLINE inline std::size_t string_bulk_run_from(const unsigned char* data, std::size_t start, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
return start + scalar_string_bulk_run(data + start, n - start);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// string_bulk_run() for the lexer. With NEON, only the first four words are
|
||||||
|
// checked inline (most keys and short values end there), and longer runs and
|
||||||
|
// non-ASCII text continue out of line. The lexer's scan function runs once per
|
||||||
|
// token, and the vector code inlined into it needs more registers, which every
|
||||||
|
// token pays for: 5% of json::accept() on jeopardy.json with Apple Clang on
|
||||||
|
// Apple M1, even though no string of that file reaches the vector code. The
|
||||||
|
// serializer keeps string_bulk_run() inline, which is faster there.
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE std::size_t lexer_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
#if JSON_SCAN_NEON && !(defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17))
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (int words = 4; words > 0 && n - i >= 8; --words)
|
||||||
|
{
|
||||||
|
const std::uint64_t special = swar_string_special(read_eight_bytes(data + i));
|
||||||
|
if (special != 0)
|
||||||
|
{
|
||||||
|
const std::size_t pos = i + (static_cast<std::size_t>(count_trailing_zeros(special)) / 8);
|
||||||
|
return data[pos] < 0x80u ? pos : string_bulk_run_from(data, pos, n);
|
||||||
|
}
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
return string_bulk_run_from(data, i, n);
|
||||||
|
#else
|
||||||
|
return string_bulk_run(data, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
// Decode the 4 hex digits at [data, data+4) - the digits following a `\u`
|
||||||
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
// escape - into a codepoint 0x0000..0xFFFF via one table lookup per byte
|
||||||
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
// (after yyjson's read_hex_u16), or return -1 if any of the 4 bytes is not a
|
||||||
|
|||||||
@@ -108,33 +108,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
m_it.object_iterator = typename object_t::iterator();
|
||||||
{
|
}
|
||||||
m_it.object_iterator = typename object_t::iterator();
|
else if (m_object->is_array())
|
||||||
break;
|
{
|
||||||
}
|
m_it.array_iterator = typename array_t::iterator();
|
||||||
|
}
|
||||||
case value_t::array:
|
else
|
||||||
{
|
{
|
||||||
m_it.array_iterator = typename array_t::iterator();
|
// primitive (is_primitive(), including discarded)
|
||||||
break;
|
m_it.primitive_iterator = primitive_iterator_t();
|
||||||
}
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
m_it.primitive_iterator = primitive_iterator_t();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,33 +234,28 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
||||||
{
|
}
|
||||||
m_it.object_iterator = m_object->m_data.m_value.object->end();
|
else if (m_object->is_array())
|
||||||
break;
|
{
|
||||||
}
|
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
m_it.primitive_iterator.set_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case value_t::array:
|
/// @throw invalid_iterator.212 if @a other points to another container
|
||||||
{
|
template<typename IterImpl>
|
||||||
m_it.array_iterator = m_object->m_data.m_value.array->end();
|
JSON_HEDLEY_ALWAYS_INLINE void check_same_container(const IterImpl& other) const
|
||||||
break;
|
{
|
||||||
}
|
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||||
|
{
|
||||||
case value_t::null:
|
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
m_it.primitive_iterator.set_end();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,10 +404,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
bool operator==(const IterImpl& other) const
|
bool operator==(const IterImpl& other) const
|
||||||
{
|
{
|
||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
check_same_container(other);
|
||||||
{
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
if (m_object == nullptr)
|
if (m_object == nullptr)
|
||||||
@@ -466,10 +443,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
bool operator<(const iter_impl& other) const
|
bool operator<(const iter_impl& other) const
|
||||||
{
|
{
|
||||||
// if objects are not the same, the comparison is undefined
|
// if objects are not the same, the comparison is undefined
|
||||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
check_same_container(other);
|
||||||
{
|
|
||||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||||
if (m_object == nullptr)
|
if (m_object == nullptr)
|
||||||
@@ -478,25 +452,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return (m_it.array_iterator < other.m_it.array_iterator);
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_object->is_array())
|
||||||
|
{
|
||||||
|
return (m_it.array_iterator < other.m_it.array_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -534,30 +501,19 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
}
|
||||||
|
|
||||||
case value_t::array:
|
if (m_object->is_array())
|
||||||
{
|
{
|
||||||
std::advance(m_it.array_iterator, i);
|
std::advance(m_it.array_iterator, i);
|
||||||
break;
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
case value_t::null:
|
// primitive (is_primitive(), including discarded)
|
||||||
case value_t::string:
|
m_it.primitive_iterator += i;
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
m_it.primitive_iterator += i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@@ -613,25 +569,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(m_object != nullptr);
|
JSON_ASSERT(m_object != nullptr);
|
||||||
|
|
||||||
switch (m_object->m_data.m_type)
|
if (m_object->is_object())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return m_it.array_iterator - other.m_it.array_iterator;
|
|
||||||
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_object->is_array())
|
||||||
|
{
|
||||||
|
return m_it.array_iterator - other.m_it.array_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// primitive (is_primitive(), including discarded)
|
||||||
|
return m_it.primitive_iterator - other.m_it.primitive_iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
@@ -109,35 +109,25 @@ template<typename IteratorType> class iteration_proxy_value
|
|||||||
{
|
{
|
||||||
JSON_ASSERT(anchor.m_object != nullptr);
|
JSON_ASSERT(anchor.m_object != nullptr);
|
||||||
|
|
||||||
switch (anchor.m_object->type())
|
if (anchor.m_object->is_array())
|
||||||
{
|
{
|
||||||
// use integer array index as key
|
// use integer array index as key
|
||||||
case value_t::array:
|
if (array_index != array_index_last)
|
||||||
{
|
{
|
||||||
if (array_index != array_index_last)
|
int_to_string( array_index_str, array_index );
|
||||||
{
|
array_index_last = array_index;
|
||||||
int_to_string( array_index_str, array_index );
|
|
||||||
array_index_last = array_index;
|
|
||||||
}
|
|
||||||
return array_index_str;
|
|
||||||
}
|
}
|
||||||
|
return array_index_str;
|
||||||
// use key from the object
|
|
||||||
case value_t::object:
|
|
||||||
return anchor.key();
|
|
||||||
|
|
||||||
// use an empty key for all primitive types
|
|
||||||
case value_t::null:
|
|
||||||
case value_t::string:
|
|
||||||
case value_t::boolean:
|
|
||||||
case value_t::number_integer:
|
|
||||||
case value_t::number_unsigned:
|
|
||||||
case value_t::number_float:
|
|
||||||
case value_t::binary:
|
|
||||||
case value_t::discarded:
|
|
||||||
default:
|
|
||||||
return empty_str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (anchor.m_object->is_object())
|
||||||
|
{
|
||||||
|
// use key from the object
|
||||||
|
return anchor.key();
|
||||||
|
}
|
||||||
|
|
||||||
|
// use an empty key for all primitive types
|
||||||
|
return empty_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return value of the iterator
|
/// return value of the iterator
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -187,6 +187,22 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// the byte order of the target where the compiler tells it (std::endian is
|
||||||
|
// C++20 and cannot select code in #if): JSON_LITTLE_ENDIAN_TARGET or
|
||||||
|
// JSON_BIG_ENDIAN_TARGET is 1, or both are 0 where it is unknown (code then
|
||||||
|
// handles the bytes one at a time); MSVC and x86 targets are little-endian
|
||||||
|
#if defined(_MSC_VER) || defined(__x86_64__) || defined(__i386__) \
|
||||||
|
|| (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#else
|
||||||
|
#define JSON_LITTLE_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#define JSON_BIG_ENDIAN_TARGET 0 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
#ifdef JSON_HAS_CPP_17
|
||||||
#define JSON_INLINE_VARIABLE inline
|
#define JSON_INLINE_VARIABLE inline
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -19,9 +19,20 @@
|
|||||||
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL
|
||||||
#undef JSON_INLINE_VARIABLE
|
#undef JSON_INLINE_VARIABLE
|
||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
|
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
||||||
#undef JSON_UNLOCKED_FILE_IO
|
#undef JSON_UNLOCKED_FILE_IO
|
||||||
#undef JSON_DTOA_SSE2
|
#undef JSON_DTOA_SSE2
|
||||||
#undef JSON_DTOA_NEON
|
#undef JSON_DTOA_NEON
|
||||||
|
#undef JSON_LITTLE_ENDIAN_TARGET
|
||||||
|
#undef JSON_BIG_ENDIAN_TARGET
|
||||||
|
#undef JSON_SCAN_NEON
|
||||||
|
#undef JSON_SCAN_SSE2
|
||||||
|
#undef JSON_SCAN_SSSE3
|
||||||
|
#undef JSON_SCAN_SSSE3_DISPATCH
|
||||||
|
#undef JSON_SCAN_SSSE3_TARGET
|
||||||
|
#undef JSON_SCAN_VECTOR
|
||||||
|
#undef JSON_SCAN_VECTOR_UTF8
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
@@ -46,8 +57,6 @@
|
|||||||
#undef JSON_STRICT_BINARY_UTF8
|
#undef JSON_STRICT_BINARY_UTF8
|
||||||
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
#undef JSON_DELETE_DEPRECATED_FUNCTIONS
|
||||||
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
#undef JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
|
||||||
#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION
|
|
||||||
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
#undef JSON_DISABLE_STREAM_BULK_SCAN
|
||||||
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
#undef JSON_DISABLE_UNLOCKED_FILE_IO
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -10,17 +10,26 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_arithmetic, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type
|
||||||
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
#include <utility> // index_sequence, make_index_sequence, index_sequence_for
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#include <algorithm> // clamp
|
||||||
|
#endif
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename T>
|
#ifdef __cpp_lib_remove_cvref
|
||||||
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
template<typename T>
|
||||||
|
using uncvref_t = std::remove_cvref_t<T>;
|
||||||
|
#else
|
||||||
|
template<typename T>
|
||||||
|
using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_14
|
#ifdef JSON_HAS_CPP_14
|
||||||
|
|
||||||
@@ -160,5 +169,19 @@ struct static_const
|
|||||||
constexpr T static_const<T>::value;
|
constexpr T static_const<T>::value;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/// @a v bounded to [@a lo, @a hi] (requires !(hi < lo)); like std::clamp,
|
||||||
|
/// but by value, so that static constexpr members can be passed without an
|
||||||
|
/// out-of-line definition before C++17
|
||||||
|
template<typename T, enable_if_t<std::is_arithmetic<T>::value, int> = 0>
|
||||||
|
constexpr T clamp(T v, T lo, T hi) noexcept
|
||||||
|
{
|
||||||
|
#ifdef __cpp_lib_clamp
|
||||||
|
return std::clamp(v, lo, hi);
|
||||||
|
#else
|
||||||
|
// NOLINTNEXTLINE(readability-avoid-nested-conditional-operator): a constant expression of C++11
|
||||||
|
return v < lo ? lo : (hi < v ? hi : v);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -313,6 +313,14 @@ struct char_traits<std::byte> : std::char_traits<char>
|
|||||||
// is_ functions //
|
// is_ functions //
|
||||||
///////////////////
|
///////////////////
|
||||||
|
|
||||||
|
#ifdef __cpp_lib_logical_traits
|
||||||
|
|
||||||
|
using std::conjunction;
|
||||||
|
using std::disjunction;
|
||||||
|
using std::negation;
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/types/conjunction
|
// https://en.cppreference.com/w/cpp/types/conjunction
|
||||||
template<class...> struct conjunction : std::true_type { };
|
template<class...> struct conjunction : std::true_type { };
|
||||||
template<class B> struct conjunction<B> : B { };
|
template<class B> struct conjunction<B> : B { };
|
||||||
@@ -330,6 +338,8 @@ struct disjunction<B, Bn...>
|
|||||||
// https://en.cppreference.com/w/cpp/types/negation
|
// https://en.cppreference.com/w/cpp/types/negation
|
||||||
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
template<class B> struct negation : std::integral_constant < bool, !B::value > { };
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
// Reimplementation of is_constructible and is_default_constructible, due to them being broken for
|
||||||
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).
|
||||||
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
// This causes compile errors in e.g., Clang 3.5 or GCC 4.9.
|
||||||
@@ -1060,8 +1070,12 @@ constexpr bool value_in_range_of(T val)
|
|||||||
return value_in_range_of_impl1<OfType, T>::test(val);
|
return value_in_range_of_impl1<OfType, T>::test(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<bool Value>
|
#ifdef __cpp_lib_bool_constant
|
||||||
using bool_constant = std::integral_constant<bool, Value>;
|
using std::bool_constant;
|
||||||
|
#else
|
||||||
|
template<bool Value>
|
||||||
|
using bool_constant = std::integral_constant<bool, Value>;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -55,6 +55,66 @@ using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
|||||||
/// output_vector_sink also appends writes of at least this size with insert()
|
/// output_vector_sink also appends writes of at least this size with insert()
|
||||||
constexpr std::size_t max_reserve_size = 256;
|
constexpr std::size_t max_reserve_size = 256;
|
||||||
|
|
||||||
|
/// @brief copy @a length characters, at most 32, without a call of memcpy()
|
||||||
|
///
|
||||||
|
/// Short strings are the typical payload of a binary value, and a call of
|
||||||
|
/// memcpy() costs more than copying them. A copy of 16 to 32 characters is two
|
||||||
|
/// possibly overlapping 16-byte copies, one of 8 to 15 two 8-byte copies, and
|
||||||
|
/// so on down to a single character; each is a fixed-size load and store.
|
||||||
|
/// Nothing outside of [src, src + length) is read or written.
|
||||||
|
template<typename CharType>
|
||||||
|
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::true_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
JSON_ASSERT(length <= 32);
|
||||||
|
if (length >= 16)
|
||||||
|
{
|
||||||
|
std::array<char, 16> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 16> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 16);
|
||||||
|
std::memcpy(last.data(), src + (length - 16), 16);
|
||||||
|
std::memcpy(dst, first.data(), 16);
|
||||||
|
std::memcpy(dst + (length - 16), last.data(), 16);
|
||||||
|
}
|
||||||
|
else if (length >= 8)
|
||||||
|
{
|
||||||
|
std::array<char, 8> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 8> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 8);
|
||||||
|
std::memcpy(last.data(), src + (length - 8), 8);
|
||||||
|
std::memcpy(dst, first.data(), 8);
|
||||||
|
std::memcpy(dst + (length - 8), last.data(), 8);
|
||||||
|
}
|
||||||
|
else if (length >= 4)
|
||||||
|
{
|
||||||
|
std::array<char, 4> first; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::array<char, 4> last; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): written before read
|
||||||
|
std::memcpy(first.data(), src, 4);
|
||||||
|
std::memcpy(last.data(), src + (length - 4), 4);
|
||||||
|
std::memcpy(dst, first.data(), 4);
|
||||||
|
std::memcpy(dst + (length - 4), last.data(), 4);
|
||||||
|
}
|
||||||
|
else if (length != 0)
|
||||||
|
{
|
||||||
|
// 1 to 3: the first, the middle, and the last character
|
||||||
|
const CharType a = src[0];
|
||||||
|
const CharType b = src[length >> 1];
|
||||||
|
const CharType c = src[length - 1];
|
||||||
|
dst[0] = a;
|
||||||
|
dst[length >> 1] = b;
|
||||||
|
dst[length - 1] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief copy @a length characters, of a character type wider than a byte
|
||||||
|
template<typename CharType>
|
||||||
|
inline void copy_short(CharType* dst, const CharType* src, const std::size_t length, std::false_type /*is_byte*/) noexcept
|
||||||
|
{
|
||||||
|
std::memcpy(dst, src, length * sizeof(CharType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// the number of characters an empty vector is grown to by output_vector_sink
|
||||||
|
constexpr std::size_t min_vector_sink_capacity = 64;
|
||||||
|
|
||||||
/// @brief non-virtual output sink writing into a std::vector
|
/// @brief non-virtual output sink writing into a std::vector
|
||||||
///
|
///
|
||||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||||
@@ -78,7 +138,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||||
: v(&vec), position(vec.size())
|
: v(&vec), cursor(vec.data() + vec.size()), limit(cursor)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
output_vector_sink(const output_vector_sink&) = delete;
|
output_vector_sink(const output_vector_sink&) = delete;
|
||||||
@@ -86,7 +146,7 @@ class output_vector_sink
|
|||||||
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
output_vector_sink& operator=(output_vector_sink&&) = delete;
|
||||||
|
|
||||||
output_vector_sink(output_vector_sink&& other) noexcept
|
output_vector_sink(output_vector_sink&& other) noexcept
|
||||||
: v(other.v), position(other.position)
|
: v(other.v), cursor(other.cursor), limit(other.limit)
|
||||||
{
|
{
|
||||||
other.v = nullptr;
|
other.v = nullptr;
|
||||||
}
|
}
|
||||||
@@ -95,7 +155,7 @@ class output_vector_sink
|
|||||||
{
|
{
|
||||||
if (v != nullptr)
|
if (v != nullptr)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
v->resize(position());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +164,11 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(position == v->size()))
|
if (JSON_HEDLEY_UNLIKELY(cursor == limit))
|
||||||
{
|
{
|
||||||
grow(1);
|
grow(1);
|
||||||
}
|
}
|
||||||
(*v)[position++] = c;
|
*cursor++ = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||||
@@ -117,7 +177,7 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
if (length >= max_reserve_size)
|
if (length >= max_reserve_size)
|
||||||
{
|
{
|
||||||
@@ -126,10 +186,15 @@ class output_vector_sink
|
|||||||
}
|
}
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
if (length != 0)
|
if (length <= 32)
|
||||||
{
|
{
|
||||||
std::memcpy(v->data() + position, s, length * sizeof(CharType));
|
copy_short(cursor, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
position += length;
|
cursor += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(cursor, s, length * sizeof(CharType));
|
||||||
|
cursor += length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,21 +209,37 @@ class output_vector_sink
|
|||||||
CharType* reserve(const std::size_t length)
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
if (JSON_HEDLEY_UNLIKELY(space() < length))
|
||||||
{
|
{
|
||||||
grow(length);
|
grow(length);
|
||||||
}
|
}
|
||||||
return v->data() + position;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// keep the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
JSON_HEDLEY_ALWAYS_INLINE
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void commit(const std::size_t length) noexcept
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
position += length;
|
cursor += length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// nothing to pass on: the characters are in the vector (see output_adapter_sink)
|
||||||
|
void flush() noexcept
|
||||||
|
{}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// number of characters of *v written so far
|
||||||
|
std::size_t position() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(cursor - v->data());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// number of characters that fit the vector behind the write position
|
||||||
|
std::size_t space() const noexcept
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(limit - cursor);
|
||||||
|
}
|
||||||
|
|
||||||
/// make room for at least @a length more characters after the position;
|
/// make room for at least @a length more characters after the position;
|
||||||
/// kept out of line so that the write functions stay small enough for
|
/// kept out of line so that the write functions stay small enough for
|
||||||
/// GCC to inline them into the writer
|
/// GCC to inline them into the writer
|
||||||
@@ -166,12 +247,15 @@ class output_vector_sink
|
|||||||
void grow(const std::size_t length)
|
void grow(const std::size_t length)
|
||||||
{
|
{
|
||||||
// use the capacity the vector already has, else at least double it;
|
// use the capacity the vector already has, else at least double it;
|
||||||
// never less than 64 characters, so that a small value does not
|
// never less than min_vector_sink_capacity, so that a small value does not
|
||||||
// reallocate for every few bytes
|
// reallocate for every few bytes
|
||||||
const std::size_t needed = position + length;
|
const std::size_t written = position();
|
||||||
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
|
const std::size_t needed = written + length;
|
||||||
|
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
|
||||||
? v->capacity()
|
? v->capacity()
|
||||||
: (std::max)(needed, v->capacity() * 2)));
|
: (std::max)(needed, v->capacity() * 2)));
|
||||||
|
cursor = v->data() + written;
|
||||||
|
limit = v->data() + v->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// append @a length (at least max_reserve_size) characters that do not fit
|
/// append @a length (at least max_reserve_size) characters that do not fit
|
||||||
@@ -181,20 +265,24 @@ class output_vector_sink
|
|||||||
JSON_HEDLEY_NEVER_INLINE
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
void append(const CharType* s, const std::size_t length)
|
void append(const CharType* s, const std::size_t length)
|
||||||
{
|
{
|
||||||
v->resize(position);
|
const std::size_t written = position();
|
||||||
if (v->capacity() - position < length)
|
v->resize(written);
|
||||||
|
if (v->capacity() - written < length)
|
||||||
{
|
{
|
||||||
// grow geometrically, with room for the writes that follow, so
|
// grow geometrically, with room for the writes that follow, so
|
||||||
// that the next grow() need not reallocate right away
|
// that the next grow() need not reallocate right away
|
||||||
v->reserve((std::max)(v->capacity() * 2, position + length + max_reserve_size));
|
v->reserve((std::max)(v->capacity() * 2, written + length + max_reserve_size));
|
||||||
}
|
}
|
||||||
v->insert(v->end(), s, s + length);
|
v->insert(v->end(), s, s + length);
|
||||||
position = v->size();
|
cursor = v->data() + v->size();
|
||||||
|
limit = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CharType, AllocatorType>* v;
|
std::vector<CharType, AllocatorType>* v;
|
||||||
/// number of characters of *v written so far
|
/// the write position: in *v, at the number of characters written so far
|
||||||
std::size_t position;
|
CharType* cursor;
|
||||||
|
/// the end of *v
|
||||||
|
CharType* limit;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// output adapter for byte vectors
|
/// output adapter for byte vectors
|
||||||
@@ -277,9 +365,17 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
|||||||
///
|
///
|
||||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
/// adapters) via the `output_adapter`-based overloads.
|
||||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
///
|
||||||
/// avoid it.
|
/// A write through the adapter is a virtual call, and for a stream a call with
|
||||||
|
/// a sentry on top, which costs more than encoding the few bytes of most values.
|
||||||
|
/// The sink therefore collects what is written in a buffer and passes it on in
|
||||||
|
/// one piece when it is full, a write is too large for it, or @ref flush is
|
||||||
|
/// called. Everything written reaches the adapter in the same order and with
|
||||||
|
/// the same bytes; the destructor passes on what is left, so that a write that
|
||||||
|
/// throws leaves the adapter with exactly the bytes written before it. Only
|
||||||
|
/// @ref flush can report an error of the adapter (the destructor must not
|
||||||
|
/// throw), so it has to be called once the value is written.
|
||||||
template<typename CharType>
|
template<typename CharType>
|
||||||
class output_adapter_sink
|
class output_adapter_sink
|
||||||
{
|
{
|
||||||
@@ -290,36 +386,121 @@ class output_adapter_sink
|
|||||||
JSON_ASSERT(oa);
|
JSON_ASSERT(oa);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output_adapter_sink(const output_adapter_sink&) = delete;
|
||||||
|
output_adapter_sink& operator=(const output_adapter_sink&) = delete;
|
||||||
|
output_adapter_sink& operator=(output_adapter_sink&&) = delete;
|
||||||
|
|
||||||
|
output_adapter_sink(output_adapter_sink&& other) noexcept
|
||||||
|
: oa(std::move(other.oa)), used(other.used)
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data(), other.buffer.data(), other.used * sizeof(CharType));
|
||||||
|
other.used = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
~output_adapter_sink()
|
||||||
|
{
|
||||||
|
// a normal return has flushed already, so this is for a write that threw
|
||||||
|
if (used != 0)
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
JSON_CATCH(...)
|
||||||
|
{
|
||||||
|
// nothing can be done about an adapter that throws here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_character(CharType c)
|
void write_character(CharType c)
|
||||||
{
|
{
|
||||||
oa->write_character(c);
|
if (JSON_HEDLEY_UNLIKELY(used == buffer_size))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
buffer[used++] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||||
// the type-erased path already did before this sink existed
|
// the type-erased path already did before this sink existed
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
void write_characters(const CharType* s, std::size_t length)
|
void write_characters(const CharType* s, std::size_t length)
|
||||||
{
|
{
|
||||||
oa->write_characters(s, length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
|
{
|
||||||
|
write_large(s, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (length <= 32)
|
||||||
|
{
|
||||||
|
copy_short(buffer.data() + used, s, length, std::integral_constant<bool, sizeof(CharType) == 1>());
|
||||||
|
used += length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data() + used, s, length * sizeof(CharType));
|
||||||
|
used += length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// space for writing up to @a length characters (see
|
/// space for writing up to @a length characters (see
|
||||||
/// output_vector_sink::reserve)
|
/// output_vector_sink::reserve)
|
||||||
CharType* reserve(const std::size_t length) noexcept
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
CharType* reserve(const std::size_t length)
|
||||||
{
|
{
|
||||||
JSON_ASSERT(length <= max_reserve_size);
|
JSON_ASSERT(length <= max_reserve_size);
|
||||||
static_cast<void>(length);
|
if (JSON_HEDLEY_UNLIKELY(buffer_size - used < length))
|
||||||
return reserved.data();
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
return buffer.data() + used;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// write the first @a length characters written to the space from reserve()
|
/// keep the first @a length characters written to the space from reserve()
|
||||||
void commit(const std::size_t length)
|
JSON_HEDLEY_ALWAYS_INLINE
|
||||||
|
void commit(const std::size_t length) noexcept
|
||||||
{
|
{
|
||||||
oa->write_characters(reserved.data(), length);
|
used += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// pass everything written so far on to the adapter
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
if (used != 0)
|
||||||
|
{
|
||||||
|
// reset first: the characters count as written even if the adapter throws
|
||||||
|
const std::size_t length = used;
|
||||||
|
used = 0;
|
||||||
|
oa->write_characters(buffer.data(), length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/// a write that does not fit the space left in the buffer
|
||||||
|
JSON_HEDLEY_NEVER_INLINE
|
||||||
|
void write_large(const CharType* s, const std::size_t length)
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
if (length >= buffer_size)
|
||||||
|
{
|
||||||
|
oa->write_characters(s, length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(buffer.data(), s, length * sizeof(CharType));
|
||||||
|
used = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// large enough for reserve() at any time
|
||||||
|
static constexpr std::size_t buffer_size = 4 * max_reserve_size;
|
||||||
|
|
||||||
output_adapter_t<CharType> oa;
|
output_adapter_t<CharType> oa;
|
||||||
std::array<CharType, max_reserve_size> reserved{};
|
/// number of characters of @ref buffer not yet passed on
|
||||||
|
std::size_t used = 0;
|
||||||
|
std::array<CharType, buffer_size> buffer{};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -48,6 +48,24 @@ inline std::string hex_byte(const std::uint8_t byte)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||||
|
|
||||||
|
The lowercase counterpart of hex_byte(), used for the `\\uXXXX` escapes of the
|
||||||
|
serializers, whose output is lowercase. Writes exactly four characters to
|
||||||
|
@a out and returns the pointer past them.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_ALWAYS_INLINE char* hex4_lower(const std::uint16_t codeunit, char* out) noexcept
|
||||||
|
{
|
||||||
|
constexpr const char* nibble_to_hex = "0123456789abcdef";
|
||||||
|
const unsigned int unit = codeunit;
|
||||||
|
out[0] = nibble_to_hex[(unit >> 12u) & 0x0Fu];
|
||||||
|
out[1] = nibble_to_hex[(unit >> 8u) & 0x0Fu];
|
||||||
|
out[2] = nibble_to_hex[(unit >> 4u) & 0x0Fu];
|
||||||
|
out[3] = nibble_to_hex[unit & 0x0Fu];
|
||||||
|
return out + 4;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// UTF-8 encoding //
|
// UTF-8 encoding //
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -103,6 +121,66 @@ void encode_utf8(std::uint32_t cp, const Out& out)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the shape of a well-formed UTF-8 sequence, by its lead byte
|
||||||
|
|
||||||
|
@a length is 1 for ASCII, 2 to 4 for a valid lead byte, and 0 for a byte that
|
||||||
|
cannot start a well-formed sequence (a continuation byte, 0xC0, 0xC1, or 0xF5
|
||||||
|
to 0xFF). For a multibyte sequence, the second byte must lie in
|
||||||
|
[@a second_lo, @a second_hi], and every later byte in [0x80, 0xBF]
|
||||||
|
(RFC 3629, section 4; Unicode Table 3-7). The narrower second-byte ranges
|
||||||
|
exclude overlong forms (0xE0, 0xF0), surrogates (0xED), and code points
|
||||||
|
beyond U+10FFFF (0xF4).
|
||||||
|
*/
|
||||||
|
struct utf8_sequence_shape
|
||||||
|
{
|
||||||
|
std::uint8_t length;
|
||||||
|
std::uint8_t second_lo;
|
||||||
|
std::uint8_t second_hi;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @return the shape of a UTF-8 sequence starting with @a lead, see utf8_sequence_shape
|
||||||
|
inline utf8_sequence_shape utf8_shape(const std::uint8_t lead) noexcept
|
||||||
|
{
|
||||||
|
if (lead < 0x80u)
|
||||||
|
{
|
||||||
|
return {1, 0, 0};
|
||||||
|
}
|
||||||
|
if (lead < 0xC2u)
|
||||||
|
{
|
||||||
|
return {0, 0, 0};
|
||||||
|
}
|
||||||
|
if (lead <= 0xDFu)
|
||||||
|
{
|
||||||
|
return {2, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xE0u)
|
||||||
|
{
|
||||||
|
return {3, 0xA0u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xEDu)
|
||||||
|
{
|
||||||
|
return {3, 0x80u, 0x9Fu};
|
||||||
|
}
|
||||||
|
if (lead <= 0xEFu)
|
||||||
|
{
|
||||||
|
return {3, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xF0u)
|
||||||
|
{
|
||||||
|
return {4, 0x90u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead <= 0xF3u)
|
||||||
|
{
|
||||||
|
return {4, 0x80u, 0xBFu};
|
||||||
|
}
|
||||||
|
if (lead == 0xF4u)
|
||||||
|
{
|
||||||
|
return {4, 0x80u, 0x8Fu};
|
||||||
|
}
|
||||||
|
return {0, 0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////
|
///////////////////
|
||||||
// UTF-8 decoding //
|
// UTF-8 decoding //
|
||||||
///////////////////
|
///////////////////
|
||||||
@@ -118,7 +196,7 @@ This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
|||||||
written by Björn Hoehrmann. See
|
written by Björn Hoehrmann. See
|
||||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||||
|
|
||||||
The library checks UTF-8 well-formedness (RFC 3629, section 4) in three
|
The library checks UTF-8 well-formedness (RFC 3629, section 4) in five
|
||||||
places, which differ in speed, diagnostics, and how they read the input:
|
places, which differ in speed, diagnostics, and how they read the input:
|
||||||
|
|
||||||
- decode() below: the serializer, to escape and, in strict mode, reject
|
- decode() below: the serializer, to escape and, in strict mode, reject
|
||||||
@@ -132,10 +210,15 @@ places, which differ in speed, diagnostics, and how they read the input:
|
|||||||
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
bulk string scan, the bulk path of the BON8 reader, and the BON8 writer.
|
||||||
They must accept exactly what the lexer's switch accepts.
|
They must accept exactly what the lexer's switch accepts.
|
||||||
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
- the byte path of binary_reader::get_bon8_string(): BON8 input without bulk
|
||||||
access, and the bytes the bulk path leaves to it.
|
access, and the bytes the bulk path leaves to it. It keeps its own ranges:
|
||||||
|
taking them from utf8_shape() made BON8 decoding 2-3% slower (#5891).
|
||||||
|
- check_utf8() in detail/view/edit.hpp: strings stored in an editable
|
||||||
|
json_document, with the index of the offending byte. It also uses
|
||||||
|
utf8_shape().
|
||||||
|
|
||||||
All four must accept the same set of sequences, so a change to one needs a
|
All five must accept the same set of sequences, which the test "UTF-8
|
||||||
matching change to the others.
|
validators accept the same sequences" in unit-class_lexer.cpp checks
|
||||||
|
exhaustively against decode() and utf8_shape().
|
||||||
|
|
||||||
@param[in,out] state the current decoder state
|
@param[in,out] state the current decoder state
|
||||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||||
|
|||||||
@@ -177,6 +177,36 @@ inline bool owns_memory(const value_t t) noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether a value of the given type is neither an object nor an array
|
||||||
|
|
||||||
|
Unlike basic_json::is_primitive(), this includes value_t::discarded: it names
|
||||||
|
the types that iterators walk with a primitive_iterator_t (a single value, or no
|
||||||
|
value for null and discarded) and that JSON Pointer resolution and the
|
||||||
|
serializer treat as leaves. Every enumerator is listed so that -Wswitch-enum
|
||||||
|
flags this function when a new type is added.
|
||||||
|
*/
|
||||||
|
inline bool is_primitive(const value_t t) noexcept
|
||||||
|
{
|
||||||
|
switch (t)
|
||||||
|
{
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case value_t::null:
|
||||||
|
case value_t::string:
|
||||||
|
case value_t::boolean:
|
||||||
|
case value_t::number_integer:
|
||||||
|
case value_t::number_unsigned:
|
||||||
|
case value_t::number_float:
|
||||||
|
case value_t::binary:
|
||||||
|
case value_t::discarded:
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief compare an integer with a floating point number without precision loss
|
@brief compare an integer with a floating point number without precision loss
|
||||||
|
|||||||
@@ -51,11 +51,6 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
|||||||
inline void check_utf8(const char* s, std::size_t n)
|
inline void check_utf8(const char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
const auto hex = [](unsigned char c)
|
|
||||||
{
|
|
||||||
constexpr const char* digits = "0123456789ABCDEF";
|
|
||||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
|
||||||
};
|
|
||||||
for (std::size_t i = 0; i < n;)
|
for (std::size_t i = 0; i < n;)
|
||||||
{
|
{
|
||||||
const unsigned char c = p[i];
|
const unsigned char c = p[i];
|
||||||
@@ -64,39 +59,22 @@ inline void check_utf8(const char* s, std::size_t n)
|
|||||||
++i;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::size_t len = 0;
|
const utf8_sequence_shape shape = utf8_shape(c);
|
||||||
unsigned char lo = 0x80;
|
const std::size_t len = shape.length;
|
||||||
unsigned char hi = 0xBF;
|
if (len == 0)
|
||||||
if (c >= 0xC2 && c <= 0xDF)
|
|
||||||
{
|
{
|
||||||
len = 2;
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
||||||
}
|
|
||||||
else if (c >= 0xE0 && c <= 0xEF)
|
|
||||||
{
|
|
||||||
len = 3;
|
|
||||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
|
||||||
hi = c == 0xED ? 0x9F : 0xBF;
|
|
||||||
}
|
|
||||||
else if (c >= 0xF0 && c <= 0xF4)
|
|
||||||
{
|
|
||||||
len = 4;
|
|
||||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
|
||||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
|
||||||
}
|
}
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
for (std::size_t k = 1; k < len; ++k)
|
||||||
{
|
{
|
||||||
if (i + k == n)
|
if (i + k == n)
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
||||||
}
|
}
|
||||||
const unsigned char b = p[i + k];
|
const unsigned char b = p[i + k];
|
||||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += len;
|
i += len;
|
||||||
@@ -144,7 +122,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||||
}
|
}
|
||||||
check_utf8(key.data(), key.size());
|
check_utf8(key.data(), key.size());
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -195,7 +173,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -212,7 +190,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||||
}
|
}
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||||
@@ -236,7 +214,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len + 1);
|
check_index(idx, a->len + 1);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -256,7 +234,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||||
}
|
}
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
@@ -274,7 +252,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
node* const h = block_of(m_doc, a, 0);
|
node* const h = block_of(m_doc, a, 0);
|
||||||
@@ -297,7 +275,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(202, "view does not belong to this document");
|
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
edit_state_of(m_doc);
|
edit_state_of(m_doc);
|
||||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
@@ -307,7 +285,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (idx >= limit)
|
if (idx >= limit)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +426,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
encoded r;
|
encoded r;
|
||||||
if (!is_container(*v.m_node))
|
if (!is_container(*v.m_node))
|
||||||
@@ -598,7 +576,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
node n = plain_node(value_t::string);
|
node n = plain_node(value_t::string);
|
||||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||||
@@ -673,12 +651,12 @@ class editor
|
|||||||
return string_node(s.data(), s.size());
|
return string_node(s.data(), s.size());
|
||||||
}
|
}
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
|||||||
constexpr std::size_t limit = 0xFFFFFFFFu;
|
constexpr std::size_t limit = 0xFFFFFFFFu;
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
|
||||||
}
|
}
|
||||||
const std::size_t needed = used + n;
|
const std::size_t needed = used + n;
|
||||||
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
||||||
@@ -135,7 +135,7 @@ inline void reserve_moved(document_data& d)
|
|||||||
document_data::edit_state& e = edit_state_of(d);
|
document_data::edit_state& e = edit_state_of(d);
|
||||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,32 +25,52 @@ namespace view
|
|||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined.
|
// small enough to be inlined.
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a parse error without a position (as those of json_pointer)
|
/// a parse error without a position (as those of json_pointer)
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// out_of_range.401: the array index @a idx is not below the size
|
||||||
|
template<typename Index>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.403: the object has no member @a key
|
||||||
|
template<typename Key>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||||
|
}
|
||||||
|
|
||||||
/// SAX handler of the error replay: accepts every event without storing it,
|
/// SAX handler of the error replay: accepts every event without storing it,
|
||||||
/// and throws the exception the parser reports
|
/// and throws the exception the parser reports
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
@@ -149,7 +169,7 @@ template<typename BasicJsonType>
|
|||||||
if (f.code == error_code::input_too_large)
|
if (f.code == error_code::input_too_large)
|
||||||
{
|
{
|
||||||
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||||
}
|
}
|
||||||
throwing_sax<BasicJsonType> sax;
|
throwing_sax<BasicJsonType> sax;
|
||||||
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
||||||
@@ -169,7 +189,7 @@ template<typename BasicJsonType>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos.chars_read_current_line = off + 1 - line_start;
|
pos.chars_read_current_line = off + 1 - line_start;
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
const std::size_t arena_size = d.arena_size;
|
const std::size_t arena_size = d.arena_size;
|
||||||
std::vector<node> compacted;
|
std::vector<node> compacted;
|
||||||
@@ -203,7 +203,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
|||||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||||
{
|
{
|
||||||
// LCOV_EXCL_START (4 GiB)
|
// LCOV_EXCL_START (4 GiB)
|
||||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
image_header h{};
|
image_header h{};
|
||||||
@@ -660,7 +660,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||||
{
|
{
|
||||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||||
@@ -668,7 +668,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
if (image == nullptr || size < sizeof(image_header))
|
if (image == nullptr || size < sizeof(image_header))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class view_iterator
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
|
||||||
}
|
}
|
||||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,51 +32,15 @@ enum class pointer_mode
|
|||||||
contains, ///< as basic_json::contains(): no exceptions at all
|
contains, ///< as basic_json::contains(): no exceptions at all
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the outcome of reading an array index from a reference token
|
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
|
||||||
enum class index_status
|
using index_status = detail::array_index_status;
|
||||||
{
|
|
||||||
ok,
|
|
||||||
leading_zero, ///< parse_error.106
|
|
||||||
not_number, ///< parse_error.109
|
|
||||||
unresolved, ///< out_of_range.404
|
|
||||||
too_large, ///< out_of_range.410
|
|
||||||
};
|
|
||||||
|
|
||||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||||
/// but reports errors instead of throwing them
|
/// but reports errors instead of throwing them
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||||
{
|
{
|
||||||
// as basic_json: the empty token and "-" (past the last element) are
|
return detail::parse_array_index(s, idx);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
for (const auto c : s)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
v = (v * 10) + d;
|
|
||||||
}
|
|
||||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
|
||||||
{
|
|
||||||
return index_status::too_large;
|
|
||||||
}
|
|
||||||
idx = static_cast<std::size_t>(v);
|
|
||||||
return index_status::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
@@ -86,15 +50,15 @@ template<typename StringType>
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case index_status::leading_zero:
|
case index_status::leading_zero:
|
||||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
|
||||||
case index_status::not_number:
|
case index_status::not_a_number:
|
||||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
|
||||||
case index_status::too_large:
|
case index_status::exceeds_size_type:
|
||||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||||
case index_status::unresolved:
|
case index_status::unresolved:
|
||||||
case index_status::ok:
|
case index_status::ok:
|
||||||
default:
|
default:
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
throw_key_not_found(token);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -131,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
throw_array_index_past_end(cur.size());
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -139,7 +103,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
const index_status status = array_index(token, idx);
|
const index_status status = array_index(token, idx);
|
||||||
if (status != index_status::ok)
|
if (status != index_status::ok)
|
||||||
{
|
{
|
||||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
|
||||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||||
{
|
{
|
||||||
throw_array_index_error(status, token);
|
throw_array_index_error(status, token);
|
||||||
@@ -150,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -160,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,17 +105,13 @@ class output_buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the size of a growth step (a function: std::min() takes a reference,
|
/// the size of a growth step
|
||||||
/// which a static constexpr member does not have before C++17)
|
static constexpr std::size_t step = 0x10000;
|
||||||
static constexpr std::size_t step() noexcept
|
|
||||||
{
|
|
||||||
return 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.reserve(estimate);
|
out.reserve(estimate);
|
||||||
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,8 +119,9 @@ class output_buffer
|
|||||||
{
|
{
|
||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation)
|
// estimate is never copied to a larger allocation; not a clamp: the
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||||
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_out.capacity())
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
@@ -171,6 +168,34 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether dump() with ensure_ascii writes the string at s unchanged, for a
|
||||||
|
/// string of the source without escape sequences (which has no quote,
|
||||||
|
/// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1
|
||||||
|
/// to each byte sets its high bit exactly for 0x7F, and a carry into the next
|
||||||
|
/// byte only comes from 0xFF, whose own high bit is set anyway.
|
||||||
|
inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
|
if (((v | (v + ones)) & high) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (static_cast<unsigned char>(s[i]) >= 0x7F)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
/// shallow documents need no allocation. The top is a pointer, as in
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -286,15 +311,19 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
if (!m_style.pretty && !m_style.ensure_ascii)
|
if (!m_style.pretty)
|
||||||
{
|
{
|
||||||
if (m_style.source_numbers)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
dump_compact<true>(root);
|
dump_compact_ascii(root);
|
||||||
|
}
|
||||||
|
else if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false>(root);
|
dump_compact<false, false>(root);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -400,6 +429,41 @@ class view_serializer
|
|||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
||||||
|
/// faster when it is not merged with this one into dump()
|
||||||
|
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true, true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_compact<false, true>(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether dump_compact() copies a string or key from the source: one of
|
||||||
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
|
/// block layout and made it 4-6% slower)
|
||||||
|
static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a string with ensure_ascii (dump_compact()), out of line
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
||||||
|
{
|
||||||
|
write_string(n);
|
||||||
|
}
|
||||||
|
|
||||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||||
/// strings, or near the end of the source); out of line, so that the
|
/// strings, or near the end of the source); out of line, so that the
|
||||||
/// compiler does not merge the fixed-size moves into this call
|
/// compiler does not merge the fixed-size moves into this call
|
||||||
@@ -417,16 +481,18 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output without ensure_ascii (the default dump())
|
@brief the compact output (the default dump(), or with ensure_ascii)
|
||||||
|
|
||||||
The same walk as dump(), with the write position in a local variable
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
buffer's members after each one), and with strings and number tokens of
|
buffer's members after each one), and with strings and number tokens of
|
||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
the source copied by fixed-size moves of 32 bytes where the source has
|
||||||
that many bytes left, instead of a library call per token. The buffer
|
that many bytes left, instead of a library call per token. The buffer
|
||||||
keeps 64 bytes of slack for the overshoot.
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
|
string of the source is copied only if it has no byte >= 0x7F (see
|
||||||
|
is_ascii_without_delete()); any other string is written by write_string().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers>
|
template<bool SourceNumbers, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_compact(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
@@ -478,7 +544,14 @@ class view_serializer
|
|||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
{
|
{
|
||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
write_decoded(n);
|
if (enabled(Ascii))
|
||||||
|
{
|
||||||
|
write_ascii_string(n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_decoded(n);
|
||||||
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
};
|
};
|
||||||
@@ -501,7 +574,7 @@ class view_serializer
|
|||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
if ((n->flags & node_flags::storage) == 0)
|
if (copied(*n, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(n->len + 2);
|
room(n->len + 2);
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
@@ -632,7 +705,7 @@ class view_serializer
|
|||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if ((key.flags & node_flags::storage) == 0)
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + 3);
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
@@ -794,8 +867,7 @@ class view_serializer
|
|||||||
static char* write_double_value_at(char* w, double x)
|
static char* write_double_value_at(char* w, double x)
|
||||||
{
|
{
|
||||||
// (from the bits: without the checks of to_chars())
|
// (from the bits: without the checks of to_chars())
|
||||||
std::uint64_t bits = 0;
|
auto bits = bit_cast<std::uint64_t>(x);
|
||||||
std::memcpy(&bits, &x, sizeof(bits));
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
{
|
{
|
||||||
return write_text_at(w, "null", 4);
|
return write_text_at(w, "null", 4);
|
||||||
@@ -818,21 +890,17 @@ class view_serializer
|
|||||||
m_out.put("null");
|
m_out.put("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static constexpr bool is_ieee_single_or_double
|
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
||||||
= 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*/)
|
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
std::array<char, 64> buf{};
|
std::array<char, 64> buf{};
|
||||||
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||||
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
// other types (e.g. long double) are rare: the library writes them
|
// other types (e.g. long double) are rare: the library writes them
|
||||||
const string_t s = BasicJsonType(x).dump();
|
const string_t s = BasicJsonType(x).dump();
|
||||||
@@ -843,9 +911,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
const char* const s = m_doc.str(n);
|
const char* const s = m_doc.str(n);
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
|
||||||
{
|
{
|
||||||
// a string of the source without escape sequences has nothing to escape
|
// a string of the source without escape sequences has nothing to
|
||||||
|
// escape (with ensure_ascii, if it is printable ASCII)
|
||||||
m_out.put(s, n.len);
|
m_out.put(s, n.len);
|
||||||
}
|
}
|
||||||
else if (m_style.ensure_ascii)
|
else if (m_style.ensure_ascii)
|
||||||
@@ -958,8 +1027,8 @@ class view_serializer
|
|||||||
|
|
||||||
void write_u_escape(std::uint32_t u)
|
void write_u_escape(std::uint32_t u)
|
||||||
{
|
{
|
||||||
static constexpr const char* hex = "0123456789abcdef";
|
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
||||||
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
||||||
m_out.put(e.data(), e.size());
|
m_out.put(e.data(), e.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,12 @@
|
|||||||
#endif
|
#endif
|
||||||
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
||||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
||||||
#include <cpuid.h>
|
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||||
|
// json_view.hpp both include it
|
||||||
|
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
#include <tmmintrin.h>
|
#include <tmmintrin.h>
|
||||||
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
std::vector<U, A> r;
|
std::vector<U, A> r;
|
||||||
r.reserve(v.size());
|
r.reserve(v.size());
|
||||||
@@ -88,7 +88,7 @@ Map map_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
Map r;
|
Map r;
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
|||||||
+728
-406
File diff suppressed because it is too large.
Load diff
@@ -267,7 +267,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||||
}
|
}
|
||||||
return lookup(key);
|
return lookup(key);
|
||||||
}
|
}
|
||||||
@@ -293,7 +293,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||||
}
|
}
|
||||||
@@ -326,12 +326,12 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
|
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -352,11 +352,11 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||||
}
|
}
|
||||||
@@ -384,7 +384,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -403,7 +403,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -421,7 +421,7 @@ class basic_json_view
|
|||||||
const iterator it = begin();
|
const iterator it = begin();
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(214, "cannot get value");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||||
}
|
}
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
@@ -573,7 +573,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), m_node->len};
|
return {m_doc->str(*m_node), m_node->len};
|
||||||
}
|
}
|
||||||
@@ -585,7 +585,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||||
}
|
}
|
||||||
@@ -795,7 +795,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||||
}
|
}
|
||||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||||
}
|
}
|
||||||
@@ -805,7 +805,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||||
}
|
}
|
||||||
@@ -814,7 +814,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -1123,7 +1123,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
||||||
}
|
}
|
||||||
return detail::view::save_image(*m_data);
|
return detail::view::save_image(*m_data);
|
||||||
}
|
}
|
||||||
@@ -1224,7 +1224,7 @@ class basic_json_document
|
|||||||
const std::size_t idx = pointer_index(token);
|
const std::size_t idx = pointer_index(token);
|
||||||
if (idx >= 0xFFFFFFFFu)
|
if (idx >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
@@ -1280,7 +1280,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (ptr.empty())
|
if (ptr.empty())
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
||||||
}
|
}
|
||||||
const view_type parent = root().at(ptr.parent_pointer());
|
const view_type parent = root().at(ptr.parent_pointer());
|
||||||
const auto& token = ptr.back();
|
const auto& token = ptr.back();
|
||||||
@@ -1300,7 +1300,7 @@ class basic_json_document
|
|||||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||||
}
|
}
|
||||||
@@ -1317,7 +1317,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(idx);
|
return static_cast<std::size_t>(idx);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,6 +125,19 @@ private:
|
|||||||
Container::pop_back();
|
Container::pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @brief remove the entry with the given key; returns the number of removed entries
|
||||||
|
template<class K>
|
||||||
|
size_type erase_impl(const K& key)
|
||||||
|
{
|
||||||
|
const auto it = find_impl(*this, key);
|
||||||
|
if (it != this->end())
|
||||||
|
{
|
||||||
|
erase_at(it);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<class V, detail::enable_if_t<
|
template<class V, detail::enable_if_t<
|
||||||
detail::is_constructible<T, V>::value, int> = 0>
|
detail::is_constructible<T, V>::value, int> = 0>
|
||||||
@@ -214,26 +227,14 @@ public:
|
|||||||
|
|
||||||
size_type erase(const key_type& key)
|
size_type erase(const key_type& key)
|
||||||
{
|
{
|
||||||
const auto it = find_impl(*this, key);
|
return erase_impl(key);
|
||||||
if (it != this->end())
|
|
||||||
{
|
|
||||||
erase_at(it);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class KeyType, detail::enable_if_t<
|
template<class KeyType, detail::enable_if_t<
|
||||||
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>
|
||||||
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
|
size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)
|
||||||
{
|
{
|
||||||
const auto it = find_impl(*this, key);
|
return erase_impl(key);
|
||||||
if (it != this->end())
|
|
||||||
{
|
|
||||||
erase_at(it);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator erase(iterator pos)
|
iterator erase(iterator pos)
|
||||||
|
|||||||
Generated
+1924
-594516
File diff suppressed because it is too large.
Load diff
Generated
+6238
-2477
File diff suppressed because it is too large.
Load diff
Generated
+10
-3
@@ -139,6 +139,12 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
#define NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD _ns
|
||||||
|
#else
|
||||||
|
#define NLOHMANN_JSON_ABI_TAG_NO_SIMD
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
#define NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD _vns
|
||||||
#else
|
#else
|
||||||
@@ -156,9 +162,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#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_EX(a, b, c, d, e, f, g, h, i, j, k, l, m) json_abi ## a ## b ## c ## d ## e ## f ## g ## h ## i ## j ## k ## l ## m
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(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, m) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(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, m)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
@@ -172,6 +178,7 @@
|
|||||||
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_STREAM_BULK_SCAN, \
|
||||||
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
NLOHMANN_JSON_ABI_TAG_DISABLE_UNLOCKED_FILE_IO, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
NLOHMANN_JSON_ABI_TAG_VIEW_NO_SIMD, \
|
||||||
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
NLOHMANN_JSON_ABI_TAG_VIEW_USE_SSSE3)
|
||||||
|
|
||||||
|
|||||||
Generated
+196
-160
@@ -690,7 +690,12 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
#endif
|
#endif
|
||||||
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
#if NLOHMANN_VIEW_SSE2 && !NLOHMANN_VIEW_SSSE3 && !defined(NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE) && ((defined(__clang__) && __clang_major__ >= 4) || (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9))))
|
||||||
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
// (GCC before 4.9 has no SSSE3 intrinsics without -mssse3)
|
||||||
#include <cpuid.h>
|
// <cpuid.h> of GCC before 11 has no include guard, and json.hpp and
|
||||||
|
// json_view.hpp both include it
|
||||||
|
#ifndef NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#define NLOHMANN_JSON_CPUID_INCLUDED
|
||||||
|
#include <cpuid.h>
|
||||||
|
#endif
|
||||||
#include <tmmintrin.h>
|
#include <tmmintrin.h>
|
||||||
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
#define NLOHMANN_VIEW_SSSE3_DISPATCH 1 // NOLINT(cppcoreguidelines-macro-to-enum,modernize-macro-to-enum)
|
||||||
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
#define NLOHMANN_VIEW_SSSE3_TARGET __attribute__((target("ssse3")))
|
||||||
@@ -2669,32 +2674,52 @@ namespace view
|
|||||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||||
// small enough to be inlined.
|
// small enough to be inlined.
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const char* prefix, const char* type)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, concat(prefix, type), nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(type_error::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_out_of_range(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(int id, const char* msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_iterator(exception_id id, const char* msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// a parse error without a position (as those of json_pointer)
|
/// a parse error without a position (as those of json_pointer)
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(int id, const std::string& msg)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||||
{
|
{
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(id, 0, msg, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// out_of_range.401: the array index @a idx is not below the size
|
||||||
|
template<typename Index>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// out_of_range.403: the object has no member @a key
|
||||||
|
template<typename Key>
|
||||||
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||||
|
{
|
||||||
|
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||||
|
}
|
||||||
|
|
||||||
/// SAX handler of the error replay: accepts every event without storing it,
|
/// SAX handler of the error replay: accepts every event without storing it,
|
||||||
/// and throws the exception the parser reports
|
/// and throws the exception the parser reports
|
||||||
template<typename BasicJsonType>
|
template<typename BasicJsonType>
|
||||||
@@ -2793,7 +2818,7 @@ template<typename BasicJsonType>
|
|||||||
if (f.code == error_code::input_too_large)
|
if (f.code == error_code::input_too_large)
|
||||||
{
|
{
|
||||||
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
// (the limit is detail::view::max_input_size(): 4 GiB minus 16 bytes)
|
||||||
NLOHMANN_VIEW_THROW(out_of_range::create(416, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
NLOHMANN_VIEW_THROW(out_of_range::create(exception_id::document_too_large, "input of 4294967280 bytes or more is not supported by json_document", nullptr));
|
||||||
}
|
}
|
||||||
throwing_sax<BasicJsonType> sax;
|
throwing_sax<BasicJsonType> sax;
|
||||||
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
const bool accepted = BasicJsonType::sax_parse(src, src + size, &sax, input_format_t::json, true, ignore_comments, ignore_trailing_commas);
|
||||||
@@ -2813,7 +2838,7 @@ template<typename BasicJsonType>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pos.chars_read_current_line = off + 1 - line_start;
|
pos.chars_read_current_line = off + 1 - line_start;
|
||||||
NLOHMANN_VIEW_THROW(parse_error::create(101, pos, "syntax error while parsing value", nullptr));
|
NLOHMANN_VIEW_THROW(parse_error::create(exception_id::syntax_error, pos, "syntax error while parsing value", nullptr));
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2888,7 +2913,7 @@ inline std::size_t text_capacity(std::size_t cap, std::size_t used, std::size_t
|
|||||||
constexpr std::size_t limit = 0xFFFFFFFFu;
|
constexpr std::size_t limit = 0xFFFFFFFFu;
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
if (NLOHMANN_VIEW_UNLIKELY(used > limit || n > limit - used))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "edits of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "edits of 4 GiB or more are not supported by json_document");
|
||||||
}
|
}
|
||||||
const std::size_t needed = used + n;
|
const std::size_t needed = used + n;
|
||||||
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
const std::size_t wanted = needed + (std::min)(limit - needed, std::size_t{256});
|
||||||
@@ -2939,7 +2964,7 @@ inline void reserve_moved(document_data& d)
|
|||||||
document_data::edit_state& e = edit_state_of(d);
|
document_data::edit_state& e = edit_state_of(d);
|
||||||
if (e.moved.size() >= 0xFFFFFFFFu)
|
if (e.moved.size() >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "more than 4294967295 edited arrays and objects are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
if (e.moved.size() == e.moved.capacity() || e.moved_cap.size() == e.moved_cap.capacity())
|
||||||
{
|
{
|
||||||
@@ -3471,11 +3496,6 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
|||||||
inline void check_utf8(const char* s, std::size_t n)
|
inline void check_utf8(const char* s, std::size_t n)
|
||||||
{
|
{
|
||||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
const auto hex = [](unsigned char c)
|
|
||||||
{
|
|
||||||
constexpr const char* digits = "0123456789ABCDEF";
|
|
||||||
return std::string{digits[c >> 4u], digits[c & 0xFu]};
|
|
||||||
};
|
|
||||||
for (std::size_t i = 0; i < n;)
|
for (std::size_t i = 0; i < n;)
|
||||||
{
|
{
|
||||||
const unsigned char c = p[i];
|
const unsigned char c = p[i];
|
||||||
@@ -3484,39 +3504,22 @@ inline void check_utf8(const char* s, std::size_t n)
|
|||||||
++i;
|
++i;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
std::size_t len = 0;
|
const utf8_sequence_shape shape = utf8_shape(c);
|
||||||
unsigned char lo = 0x80;
|
const std::size_t len = shape.length;
|
||||||
unsigned char hi = 0xBF;
|
if (len == 0)
|
||||||
if (c >= 0xC2 && c <= 0xDF)
|
|
||||||
{
|
{
|
||||||
len = 2;
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex_byte(c)));
|
||||||
}
|
|
||||||
else if (c >= 0xE0 && c <= 0xEF)
|
|
||||||
{
|
|
||||||
len = 3;
|
|
||||||
lo = c == 0xE0 ? 0xA0 : 0x80;
|
|
||||||
hi = c == 0xED ? 0x9F : 0xBF;
|
|
||||||
}
|
|
||||||
else if (c >= 0xF0 && c <= 0xF4)
|
|
||||||
{
|
|
||||||
len = 4;
|
|
||||||
lo = c == 0xF0 ? 0x90 : 0x80;
|
|
||||||
hi = c == 0xF4 ? 0x8F : 0xBF;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i), ": 0x", hex(c)));
|
|
||||||
}
|
}
|
||||||
for (std::size_t k = 1; k < len; ++k)
|
for (std::size_t k = 1; k < len; ++k)
|
||||||
{
|
{
|
||||||
if (i + k == n)
|
if (i + k == n)
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("incomplete UTF-8 string; last byte: 0x", hex(p[n - 1])));
|
throw_type_error(exception_id::invalid_utf8, concat("incomplete UTF-8 string; last byte: 0x", hex_byte(p[n - 1])));
|
||||||
}
|
}
|
||||||
const unsigned char b = p[i + k];
|
const unsigned char b = p[i + k];
|
||||||
if (b < (k == 1 ? lo : 0x80) || b > (k == 1 ? hi : 0xBF))
|
if (b < (k == 1 ? shape.second_lo : 0x80) || b > (k == 1 ? shape.second_hi : 0xBF))
|
||||||
{
|
{
|
||||||
throw_type_error(316, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex(b)));
|
throw_type_error(exception_id::invalid_utf8, concat("invalid UTF-8 byte at index ", std::to_string(i + k), ": 0x", hex_byte(b)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += len;
|
i += len;
|
||||||
@@ -3564,7 +3567,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object) && o->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a string argument with ", object.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||||
}
|
}
|
||||||
check_utf8(key.data(), key.size());
|
check_utf8(key.data(), key.size());
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -3615,7 +3618,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(305, "cannot use operator[] with a numeric argument with ", array.type_name());
|
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -3632,7 +3635,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array) && a->kind != static_cast<std::uint8_t>(value_t::null))
|
||||||
{
|
{
|
||||||
throw_type_error(308, "cannot use push_back() with ", array.type_name());
|
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||||
}
|
}
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
if (a->kind == static_cast<std::uint8_t>(value_t::null))
|
||||||
@@ -3656,7 +3659,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(309, "cannot use insert() with ", array.type_name());
|
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len + 1);
|
check_index(idx, a->len + 1);
|
||||||
const encoded e = encode(std::forward<V>(value));
|
const encoded e = encode(std::forward<V>(value));
|
||||||
@@ -3676,7 +3679,7 @@ class editor
|
|||||||
node* const o = own(object);
|
node* const o = own(object);
|
||||||
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
if (o->kind != static_cast<std::uint8_t>(value_t::object))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", object.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||||
}
|
}
|
||||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||||
{
|
{
|
||||||
@@ -3694,7 +3697,7 @@ class editor
|
|||||||
node* const a = own(array);
|
node* const a = own(array);
|
||||||
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
if (a->kind != static_cast<std::uint8_t>(value_t::array))
|
||||||
{
|
{
|
||||||
throw_type_error(307, "cannot use erase() with ", array.type_name());
|
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||||
}
|
}
|
||||||
check_index(idx, a->len);
|
check_index(idx, a->len);
|
||||||
node* const h = block_of(m_doc, a, 0);
|
node* const h = block_of(m_doc, a, 0);
|
||||||
@@ -3717,7 +3720,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_doc != &m_doc || v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(202, "view does not belong to this document");
|
throw_invalid_iterator(exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
edit_state_of(m_doc);
|
edit_state_of(m_doc);
|
||||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||||
@@ -3727,7 +3730,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (idx >= limit)
|
if (idx >= limit)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3868,7 +3871,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(v.m_node == nullptr))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
encoded r;
|
encoded r;
|
||||||
if (!is_container(*v.m_node))
|
if (!is_container(*v.m_node))
|
||||||
@@ -4018,7 +4021,7 @@ class editor
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
if (NLOHMANN_VIEW_UNLIKELY(len >= 0xFFFFFFFFu))
|
||||||
{
|
{
|
||||||
throw_out_of_range(416, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::document_too_large, "strings of 4 GiB or more are not supported by json_document"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
node n = plain_node(value_t::string);
|
node n = plain_node(value_t::string);
|
||||||
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
n.flags = static_cast<std::uint8_t>(n.flags | node_flags::edited);
|
||||||
@@ -4093,12 +4096,12 @@ class editor
|
|||||||
return string_node(s.data(), s.size());
|
return string_node(s.data(), s.size());
|
||||||
}
|
}
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
throw_type_error(319, "cannot store a binary value in a json_document", "");
|
throw_type_error(exception_id::document_binary_value, "cannot store a binary value in a json_document", "");
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be a value, but is ", "discarded");
|
throw_type_error(exception_id::type_mismatch, "type must be a value, but is ", "discarded");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4726,7 +4729,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
|||||||
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
inline std::vector<std::uint8_t> save_image(const document_data& d)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
const std::size_t arena_size = d.arena_size;
|
const std::size_t arena_size = d.arena_size;
|
||||||
std::vector<node> compacted;
|
std::vector<node> compacted;
|
||||||
@@ -4743,7 +4746,7 @@ inline std::vector<std::uint8_t> save_image(const document_data& d)
|
|||||||
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
if (NLOHMANN_VIEW_UNLIKELY(text_size >= image_limit || total_arena >= image_limit || count >= image_limit))
|
||||||
{
|
{
|
||||||
// LCOV_EXCL_START (4 GiB)
|
// LCOV_EXCL_START (4 GiB)
|
||||||
throw_out_of_range(416, "images of 4 GiB or more are not supported by json_document");
|
throw_out_of_range(exception_id::document_too_large, "images of 4 GiB or more are not supported by json_document");
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
image_header h{};
|
image_header h{};
|
||||||
@@ -5200,7 +5203,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
|
|
||||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_invalid_image(const char* what)
|
||||||
{
|
{
|
||||||
throw_parse_error(116, concat("invalid json_document image: ", what));
|
throw_parse_error(exception_id::invalid_document_image, concat("invalid json_document image: ", what));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read an image into d. The text and the decoded strings stay in the image;
|
/// Read an image into d. The text and the decoded strings stay in the image;
|
||||||
@@ -5208,7 +5211,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
|||||||
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
inline void load_image(document_data& d, const std::uint8_t* image, std::size_t size, image_check check)
|
||||||
{
|
{
|
||||||
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
#if !NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||||
throw_type_error(320, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
throw_type_error(exception_id::document_image_unsupported, "json_document images need a little-endian target"); // LCOV_EXCL_LINE
|
||||||
#endif
|
#endif
|
||||||
if (image == nullptr || size < sizeof(image_header))
|
if (image == nullptr || size < sizeof(image_header))
|
||||||
{
|
{
|
||||||
@@ -5562,7 +5565,7 @@ class view_iterator
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||||
{
|
{
|
||||||
throw_invalid_iterator(207, "cannot use key() for non-object iterators");
|
throw_invalid_iterator(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators");
|
||||||
}
|
}
|
||||||
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
return string_view_t(m_doc->str(*m_pos), m_pos->len);
|
||||||
}
|
}
|
||||||
@@ -5914,51 +5917,15 @@ enum class pointer_mode
|
|||||||
contains, ///< as basic_json::contains(): no exceptions at all
|
contains, ///< as basic_json::contains(): no exceptions at all
|
||||||
};
|
};
|
||||||
|
|
||||||
/// the outcome of reading an array index from a reference token
|
/// the outcome of reading an array index: detail::parse_array_index(), shared with json_pointer
|
||||||
enum class index_status
|
using index_status = detail::array_index_status;
|
||||||
{
|
|
||||||
ok,
|
|
||||||
leading_zero, ///< parse_error.106
|
|
||||||
not_number, ///< parse_error.109
|
|
||||||
unresolved, ///< out_of_range.404
|
|
||||||
too_large, ///< out_of_range.410
|
|
||||||
};
|
|
||||||
|
|
||||||
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
/// reads an array index like json_pointer::array_index (RFC 6901, Sect. 4),
|
||||||
/// but reports errors instead of throwing them
|
/// but reports errors instead of throwing them
|
||||||
template<typename StringType>
|
template<typename StringType>
|
||||||
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||||
{
|
{
|
||||||
// as basic_json: the empty token and "-" (past the last element) are
|
return detail::parse_array_index(s, idx);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
std::uint64_t v = 0;
|
|
||||||
for (const auto c : s)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
v = (v * 10) + d;
|
|
||||||
}
|
|
||||||
if (v >= (std::numeric_limits<std::size_t>::max)()) // (std::size_t converts to std::uint64_t implicitly)
|
|
||||||
{
|
|
||||||
return index_status::too_large;
|
|
||||||
}
|
|
||||||
idx = static_cast<std::size_t>(v);
|
|
||||||
return index_status::ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// throws the exception json_pointer::array_index throws for this status
|
/// throws the exception json_pointer::array_index throws for this status
|
||||||
@@ -5968,15 +5935,15 @@ template<typename StringType>
|
|||||||
switch (status)
|
switch (status)
|
||||||
{
|
{
|
||||||
case index_status::leading_zero:
|
case index_status::leading_zero:
|
||||||
throw_parse_error(106, concat("array index '", s, "' must not begin with '0'"));
|
throw_parse_error(exception_id::pointer_index_leading_zero, concat("array index '", s, "' must not begin with '0'"));
|
||||||
case index_status::not_number:
|
case index_status::not_a_number:
|
||||||
throw_parse_error(109, concat("array index '", s, "' is not a number"));
|
throw_parse_error(exception_id::pointer_index_not_a_number, concat("array index '", s, "' is not a number"));
|
||||||
case index_status::too_large:
|
case index_status::exceeds_size_type:
|
||||||
throw_out_of_range(410, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
throw_out_of_range(exception_id::value_out_of_range, concat("array index ", s, " exceeds size_type")); // LCOV_EXCL_LINE
|
||||||
case index_status::unresolved:
|
case index_status::unresolved:
|
||||||
case index_status::ok:
|
case index_status::ok:
|
||||||
default:
|
default:
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", s, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", s, "'"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6001,7 +5968,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(403, concat("key '", token, "' not found"));
|
throw_key_not_found(token);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -6013,7 +5980,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(402, concat("array index '-' (", std::to_string(cur.size()), ") is out of range"));
|
throw_array_index_past_end(cur.size());
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -6021,7 +5988,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
const index_status status = array_index(token, idx);
|
const index_status status = array_index(token, idx);
|
||||||
if (status != index_status::ok)
|
if (status != index_status::ok)
|
||||||
{
|
{
|
||||||
const bool parse_error = status == index_status::leading_zero || status == index_status::not_number;
|
const bool parse_error = status == index_status::leading_zero || status == index_status::not_a_number;
|
||||||
if (throwing || (mode == pointer_mode::value && parse_error))
|
if (throwing || (mode == pointer_mode::value && parse_error))
|
||||||
{
|
{
|
||||||
throw_array_index_error(status, token);
|
throw_array_index_error(status, token);
|
||||||
@@ -6032,7 +5999,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (mode == pointer_mode::checked)
|
if (mode == pointer_mode::checked)
|
||||||
{
|
{
|
||||||
throw_out_of_range(401, concat("array index ", std::to_string(idx), " is out of range"));
|
throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -6042,7 +6009,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
|||||||
{
|
{
|
||||||
if (throwing)
|
if (throwing)
|
||||||
{
|
{
|
||||||
throw_out_of_range(404, concat("unresolved reference token '", token, "'"));
|
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||||
}
|
}
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
@@ -6166,17 +6133,13 @@ class output_buffer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// the size of a growth step (a function: std::min() takes a reference,
|
/// the size of a growth step
|
||||||
/// which a static constexpr member does not have before C++17)
|
static constexpr std::size_t step = 0x10000;
|
||||||
static constexpr std::size_t step() noexcept
|
|
||||||
{
|
|
||||||
return 0x10000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static StringType& sized(StringType& out, std::size_t estimate)
|
static StringType& sized(StringType& out, std::size_t estimate)
|
||||||
{
|
{
|
||||||
out.reserve(estimate);
|
out.reserve(estimate);
|
||||||
out.resize((std::min)((std::max)(estimate, static_cast<std::size_t>(64)), step()));
|
out.resize(detail::clamp(estimate, std::size_t{64}, step));
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6184,8 +6147,9 @@ class output_buffer
|
|||||||
{
|
{
|
||||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||||
// (a step does not go beyond the reserved estimate, so that a good
|
// (a step does not go beyond the reserved estimate, so that a good
|
||||||
// estimate is never copied to a larger allocation)
|
// estimate is never copied to a larger allocation; not a clamp: the
|
||||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step(), m_out.capacity()), used + n + 256);
|
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||||
|
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||||
if (size > m_out.capacity())
|
if (size > m_out.capacity())
|
||||||
{
|
{
|
||||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||||
@@ -6232,6 +6196,34 @@ inline std::size_t plain_output_run(const unsigned char* s, std::size_t n) noexc
|
|||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether dump() with ensure_ascii writes the string at s unchanged, for a
|
||||||
|
/// string of the source without escape sequences (which has no quote,
|
||||||
|
/// backslash, or control character): no byte is DEL (0x7F) or above. Adding 1
|
||||||
|
/// to each byte sets its high bit exactly for 0x7F, and a carry into the next
|
||||||
|
/// byte only comes from 0xFF, whose own high bit is set anyway.
|
||||||
|
inline bool is_ascii_without_delete(const char* s, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
const std::uint64_t v = read_eight_bytes(s + i);
|
||||||
|
if (((v | (v + ones)) & high) != 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (static_cast<unsigned char>(s[i]) >= 0x7F)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
/// A stack that starts in a buffer of the caller (a local array) and moves to
|
||||||
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
/// the heap (a vector of the caller) only when that is full, so that dumps of
|
||||||
/// shallow documents need no allocation. The top is a pointer, as in
|
/// shallow documents need no allocation. The top is a pointer, as in
|
||||||
@@ -6347,15 +6339,19 @@ class view_serializer
|
|||||||
|
|
||||||
void dump(const node* root)
|
void dump(const node* root)
|
||||||
{
|
{
|
||||||
if (!m_style.pretty && !m_style.ensure_ascii)
|
if (!m_style.pretty)
|
||||||
{
|
{
|
||||||
if (m_style.source_numbers)
|
if (m_style.ensure_ascii)
|
||||||
{
|
{
|
||||||
dump_compact<true>(root);
|
dump_compact_ascii(root);
|
||||||
|
}
|
||||||
|
else if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true, false>(root);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
dump_compact<false>(root);
|
dump_compact<false, false>(root);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -6461,6 +6457,41 @@ class view_serializer
|
|||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// dump_compact() with ensure_ascii, out of line: the walk without it is
|
||||||
|
/// faster when it is not merged with this one into dump()
|
||||||
|
NLOHMANN_VIEW_NOINLINE void dump_compact_ascii(const node* root)
|
||||||
|
{
|
||||||
|
if (m_style.source_numbers)
|
||||||
|
{
|
||||||
|
dump_compact<true, true>(root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dump_compact<false, true>(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// whether dump_compact() copies a string or key from the source: one of
|
||||||
|
/// the source, and with ensure_ascii one without bytes >= 0x7F (an overload
|
||||||
|
/// each, so that the walk without ensure_ascii is compiled exactly as
|
||||||
|
/// before: with GCC, a condition that only folds away later changed its
|
||||||
|
/// block layout and made it 4-6% slower)
|
||||||
|
static bool copied(const node& n, const char* /*src*/, std::false_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool copied(const node& n, const char* src, std::true_type /*ensure_ascii*/) noexcept
|
||||||
|
{
|
||||||
|
return (n.flags & node_flags::storage) == 0 && is_ascii_without_delete(src + n.off, n.len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// a string with ensure_ascii (dump_compact()), out of line
|
||||||
|
NLOHMANN_VIEW_NOINLINE void write_ascii_string(const node& n)
|
||||||
|
{
|
||||||
|
write_string(n);
|
||||||
|
}
|
||||||
|
|
||||||
/// the copies of dump_compact() that are not fixed-size moves (long
|
/// the copies of dump_compact() that are not fixed-size moves (long
|
||||||
/// strings, or near the end of the source); out of line, so that the
|
/// strings, or near the end of the source); out of line, so that the
|
||||||
/// compiler does not merge the fixed-size moves into this call
|
/// compiler does not merge the fixed-size moves into this call
|
||||||
@@ -6478,16 +6509,18 @@ class view_serializer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief the compact output without ensure_ascii (the default dump())
|
@brief the compact output (the default dump(), or with ensure_ascii)
|
||||||
|
|
||||||
The same walk as dump(), with the write position in a local variable
|
The same walk as dump(), with the write position in a local variable
|
||||||
(stores through char pointers would otherwise force a reload of the
|
(stores through char pointers would otherwise force a reload of the
|
||||||
buffer's members after each one), and with strings and number tokens of
|
buffer's members after each one), and with strings and number tokens of
|
||||||
the source copied by fixed-size moves of 32 bytes where the source has
|
the source copied by fixed-size moves of 32 bytes where the source has
|
||||||
that many bytes left, instead of a library call per token. The buffer
|
that many bytes left, instead of a library call per token. The buffer
|
||||||
keeps 64 bytes of slack for the overshoot.
|
keeps 64 bytes of slack for the overshoot. With ensure_ascii (Ascii), a
|
||||||
|
string of the source is copied only if it has no byte >= 0x7F (see
|
||||||
|
is_ascii_without_delete()); any other string is written by write_string().
|
||||||
*/
|
*/
|
||||||
template<bool SourceNumbers>
|
template<bool SourceNumbers, bool Ascii>
|
||||||
void dump_compact(const node* root)
|
void dump_compact(const node* root)
|
||||||
{
|
{
|
||||||
struct frame
|
struct frame
|
||||||
@@ -6539,7 +6572,14 @@ class view_serializer
|
|||||||
const auto escaped = [&](const node & n)
|
const auto escaped = [&](const node & n)
|
||||||
{
|
{
|
||||||
m_out.set_cursor(w);
|
m_out.set_cursor(w);
|
||||||
write_decoded(n);
|
if (enabled(Ascii))
|
||||||
|
{
|
||||||
|
write_ascii_string(n);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_decoded(n);
|
||||||
|
}
|
||||||
w = m_out.cursor();
|
w = m_out.cursor();
|
||||||
lim = m_out.limit();
|
lim = m_out.limit();
|
||||||
};
|
};
|
||||||
@@ -6562,7 +6602,7 @@ class view_serializer
|
|||||||
switch (static_cast<value_t>(n->kind))
|
switch (static_cast<value_t>(n->kind))
|
||||||
{
|
{
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
if ((n->flags & node_flags::storage) == 0)
|
if (copied(*n, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(n->len + 2);
|
room(n->len + 2);
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
@@ -6693,7 +6733,7 @@ class view_serializer
|
|||||||
if (cur.object)
|
if (cur.object)
|
||||||
{
|
{
|
||||||
const node& key = *at;
|
const node& key = *at;
|
||||||
if ((key.flags & node_flags::storage) == 0)
|
if (copied(key, src, std::integral_constant<bool, Ascii>()))
|
||||||
{
|
{
|
||||||
room(key.len + 3);
|
room(key.len + 3);
|
||||||
*w++ = '"';
|
*w++ = '"';
|
||||||
@@ -6855,8 +6895,7 @@ class view_serializer
|
|||||||
static char* write_double_value_at(char* w, double x)
|
static char* write_double_value_at(char* w, double x)
|
||||||
{
|
{
|
||||||
// (from the bits: without the checks of to_chars())
|
// (from the bits: without the checks of to_chars())
|
||||||
std::uint64_t bits = 0;
|
auto bits = bit_cast<std::uint64_t>(x);
|
||||||
std::memcpy(&bits, &x, sizeof(bits));
|
|
||||||
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
if (NLOHMANN_VIEW_UNLIKELY((bits & 0x7FF0000000000000u) == 0x7FF0000000000000u))
|
||||||
{
|
{
|
||||||
return write_text_at(w, "null", 4);
|
return write_text_at(w, "null", 4);
|
||||||
@@ -6879,21 +6918,17 @@ class view_serializer
|
|||||||
m_out.put("null");
|
m_out.put("null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
static constexpr bool is_ieee_single_or_double
|
write_float(x, std::integral_constant<bool, ::nlohmann::detail::has_native_float_format<number_float_t>::value>());
|
||||||
= 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*/)
|
void write_float(number_float_t x, std::true_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
std::array<char, 64> buf{};
|
std::array<char, 64> buf{};
|
||||||
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
const char* const end = ::nlohmann::detail::to_chars(buf.data(), buf.data() + buf.size(), x);
|
||||||
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
m_out.put(buf.data(), static_cast<std::size_t>(end - buf.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void write_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)
|
void write_float(number_float_t x, std::false_type /*has_native_float_format*/)
|
||||||
{
|
{
|
||||||
// other types (e.g. long double) are rare: the library writes them
|
// other types (e.g. long double) are rare: the library writes them
|
||||||
const string_t s = BasicJsonType(x).dump();
|
const string_t s = BasicJsonType(x).dump();
|
||||||
@@ -6904,9 +6939,10 @@ class view_serializer
|
|||||||
{
|
{
|
||||||
const char* const s = m_doc.str(n);
|
const char* const s = m_doc.str(n);
|
||||||
m_out.put('"');
|
m_out.put('"');
|
||||||
if ((n.flags & node_flags::storage) == 0 && !m_style.ensure_ascii)
|
if ((n.flags & node_flags::storage) == 0 && (!m_style.ensure_ascii || is_ascii_without_delete(s, n.len)))
|
||||||
{
|
{
|
||||||
// a string of the source without escape sequences has nothing to escape
|
// a string of the source without escape sequences has nothing to
|
||||||
|
// escape (with ensure_ascii, if it is printable ASCII)
|
||||||
m_out.put(s, n.len);
|
m_out.put(s, n.len);
|
||||||
}
|
}
|
||||||
else if (m_style.ensure_ascii)
|
else if (m_style.ensure_ascii)
|
||||||
@@ -7019,8 +7055,8 @@ class view_serializer
|
|||||||
|
|
||||||
void write_u_escape(std::uint32_t u)
|
void write_u_escape(std::uint32_t u)
|
||||||
{
|
{
|
||||||
static constexpr const char* hex = "0123456789abcdef";
|
std::array<char, 6> e = {{'\\', 'u', 0, 0, 0, 0}};
|
||||||
const std::array<char, 6> e = {{'\\', 'u', hex[(u >> 12u) & 0xFu], hex[(u >> 8u) & 0xFu], hex[(u >> 4u) & 0xFu], hex[u & 0xFu]}};
|
hex4_lower(static_cast<std::uint16_t>(u), &e[2]);
|
||||||
m_out.put(e.data(), e.size());
|
m_out.put(e.data(), e.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7217,7 +7253,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
|||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
throw_type_error(302, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7227,7 +7263,7 @@ std::vector<U, A> vector_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be array, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
std::vector<U, A> r;
|
std::vector<U, A> r;
|
||||||
r.reserve(v.size());
|
r.reserve(v.size());
|
||||||
@@ -7245,7 +7281,7 @@ Map map_value(const View& v)
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||||
{
|
{
|
||||||
throw_type_error(302, "type must be object, but is ", v.type_name());
|
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||||
}
|
}
|
||||||
Map r;
|
Map r;
|
||||||
for (auto it = v.begin(); it != v.end(); ++it)
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
@@ -7484,7 +7520,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a string argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||||
}
|
}
|
||||||
return lookup(key);
|
return lookup(key);
|
||||||
}
|
}
|
||||||
@@ -7510,7 +7546,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
detail::view::throw_type_error(305, "cannot use operator[] with a numeric argument with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||||
}
|
}
|
||||||
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
return idx < m_node->len ? basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx))) : basic_json_view();
|
||||||
}
|
}
|
||||||
@@ -7543,12 +7579,12 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(403, detail::concat("key '", std::string(key.data(), key.size()), "' not found"));
|
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -7569,11 +7605,11 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(304, "cannot use at() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||||
}
|
}
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
return basic_json_view(m_doc, navigation::value(detail::view::element_at<Editable>(*m_doc, m_node, idx)));
|
||||||
}
|
}
|
||||||
@@ -7601,7 +7637,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = lookup(key);
|
const basic_json_view r = lookup(key);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -7620,7 +7656,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(306, "cannot use value() with ", type_name());
|
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||||
}
|
}
|
||||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||||
return r.is_discarded() ? default_value : r.template get<T>();
|
return r.is_discarded() ? default_value : r.template get<T>();
|
||||||
@@ -7638,7 +7674,7 @@ class basic_json_view
|
|||||||
const iterator it = begin();
|
const iterator it = begin();
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(214, "cannot get value");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||||
}
|
}
|
||||||
return *it;
|
return *it;
|
||||||
}
|
}
|
||||||
@@ -7790,7 +7826,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be string, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), m_node->len};
|
return {m_doc->str(*m_node), m_node->len};
|
||||||
}
|
}
|
||||||
@@ -7802,7 +7838,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||||
}
|
}
|
||||||
@@ -8012,7 +8048,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be boolean, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||||
}
|
}
|
||||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||||
}
|
}
|
||||||
@@ -8022,7 +8058,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be number, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||||
}
|
}
|
||||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||||
}
|
}
|
||||||
@@ -8031,7 +8067,7 @@ class basic_json_view
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(302, "type must be null, but is ", type_name());
|
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -8340,7 +8376,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_type_error(320, "cannot save a discarded json_document");
|
detail::view::throw_type_error(detail::exception_id::document_image_unsupported, "cannot save a discarded json_document");
|
||||||
}
|
}
|
||||||
return detail::view::save_image(*m_data);
|
return detail::view::save_image(*m_data);
|
||||||
}
|
}
|
||||||
@@ -8441,7 +8477,7 @@ class basic_json_document
|
|||||||
const std::size_t idx = pointer_index(token);
|
const std::size_t idx = pointer_index(token);
|
||||||
if (idx >= 0xFFFFFFFFu)
|
if (idx >= 0xFFFFFFFFu)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < idx; ++i)
|
for (std::size_t i = 0; i < idx; ++i)
|
||||||
{
|
{
|
||||||
@@ -8497,7 +8533,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (ptr.empty())
|
if (ptr.empty())
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(405, "JSON pointer has no parent");
|
detail::view::throw_out_of_range(detail::exception_id::patch_on_root, "JSON pointer has no parent");
|
||||||
}
|
}
|
||||||
const view_type parent = root().at(ptr.parent_pointer());
|
const view_type parent = root().at(ptr.parent_pointer());
|
||||||
const auto& token = ptr.back();
|
const auto& token = ptr.back();
|
||||||
@@ -8517,7 +8553,7 @@ class basic_json_document
|
|||||||
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
static_assert(Editable, "only an editable document can be edited: use basic_json_document<BasicJsonType, true> (json_editable_document)");
|
||||||
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
if (NLOHMANN_VIEW_UNLIKELY(!m_data || m_data->discarded))
|
||||||
{
|
{
|
||||||
detail::view::throw_invalid_iterator(202, "view does not belong to this document");
|
detail::view::throw_invalid_iterator(detail::exception_id::iterator_from_other_value, "view does not belong to this document");
|
||||||
}
|
}
|
||||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||||
}
|
}
|
||||||
@@ -8534,7 +8570,7 @@ class basic_json_document
|
|||||||
{
|
{
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
{
|
{
|
||||||
detail::view::throw_out_of_range(401, detail::concat("array index ", std::to_string(idx), " is out of range"));
|
detail::view::throw_array_index_out_of_range(idx);
|
||||||
}
|
}
|
||||||
return static_cast<std::size_t>(idx);
|
return static_cast<std::size_t>(idx);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-36
@@ -2,14 +2,12 @@ cmake_minimum_required(VERSION 3.13...4.0)
|
|||||||
|
|
||||||
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
option(JSON_Valgrind "Execute test suite with Valgrind." OFF)
|
||||||
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
option(JSON_FastTests "Skip expensive/slow tests." OFF)
|
||||||
option(JSON_TestUnityBuild "Build the unit tests in batches of several files per executable to speed up compilation." ON)
|
|
||||||
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
option(JSON_TestSimdutf "Build the unit tests against the simdutf UTF-8 validation backend." OFF)
|
||||||
|
|
||||||
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
set(JSON_SIMDUTF_VERSION 9.1.0 CACHE STRING "The simdutf version used by JSON_TestSimdutf.")
|
||||||
|
|
||||||
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
set(JSON_32bitTest AUTO CACHE STRING "Enable the 32bit unit test (ON/OFF/AUTO/ONLY).")
|
||||||
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
set(JSON_TestStandards "" CACHE STRING "The list of standards to test explicitly.")
|
||||||
set(JSON_TestUnityBatchSize 8 CACHE STRING "The number of unit test files compiled into one executable with JSON_TestUnityBuild.")
|
|
||||||
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
set(JSON_TestShard "" CACHE STRING "Build only a part of the unit tests, given as <index>/<count> (e.g. 0/2), to split them across CI jobs with a time limit.")
|
||||||
|
|
||||||
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
# using an env var, since this will also affect targets executing cmake (such as "ci_test_compiler_default")
|
||||||
@@ -22,11 +20,6 @@ endif()
|
|||||||
|
|
||||||
include(test)
|
include(test)
|
||||||
|
|
||||||
# MinGW's GNU ld hits section and relocation limits with the large merged translation units
|
|
||||||
if(MINGW)
|
|
||||||
set(JSON_TestUnityBuild OFF)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# override standard support
|
# override standard support
|
||||||
#############################################################################
|
#############################################################################
|
||||||
@@ -333,28 +326,9 @@ if(NOT "${JSON_TestShard}" STREQUAL "")
|
|||||||
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
message(STATUS "Test shard ${JSON_TestShard}: ${shard_file_count} of ${test_file_count} unit test files")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Explicit groups of test files (file names without "unit-" and ".cpp") for
|
foreach(file ${files})
|
||||||
# JSON_TestUnityBuild: all files of a group are compiled into one translation
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
# unit per C++ standard, so that related tests share their template
|
endforeach()
|
||||||
# instantiations (all binary formats instantiate the same readers and
|
|
||||||
# writers). Only add a group where this was measured to pay off: grouping
|
|
||||||
# files that share little just creates one slow translation unit. Files in no
|
|
||||||
# group are batched alphabetically by JSON_TestUnityBatchSize.
|
|
||||||
set(json_test_unity_groups binary)
|
|
||||||
set(json_test_unity_group_binary binary_formats binary_utf8_error_handler binary_writer_sinks bjdata bon8 bon8-cpp17 bson byte_container_with_subtype cbor msgpack msgpack-cpp17 ubjson)
|
|
||||||
|
|
||||||
if(JSON_TestUnityBuild)
|
|
||||||
message(STATUS "Building the unit tests in batches of ${JSON_TestUnityBatchSize} files (JSON_TestUnityBuild)")
|
|
||||||
json_test_add_unity_tests(FILES ${files} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
BATCH_SIZE ${JSON_TestUnityBatchSize}
|
|
||||||
GROUPS ${json_test_unity_groups}
|
|
||||||
VARIANT_FILES src/unit-comparison.cpp src/unit-comparison-cpp20.cpp src/unit-class_parser.cpp src/unit-diagnostic-positions.cpp
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
foreach(file ${files})
|
|
||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
|
||||||
endforeach()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
# tests/src/unit-no-macro-leak.cpp #include-s the generated leak-check file,
|
||||||
# so its test targets must be built after generate_hedley_undef_checks.
|
# so its test targets must be built after generate_hedley_undef_checks.
|
||||||
@@ -380,13 +354,6 @@ json_test_add_test_for(src/unit-comparison.cpp
|
|||||||
NAME test-comparison_legacy
|
NAME test-comparison_legacy
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
json_test_set_test_options(test-comparison_legacy-cpp20
|
|
||||||
COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
|
||||||
)
|
|
||||||
json_test_add_test_for(src/unit-comparison-cpp20.cpp
|
|
||||||
NAME test-comparison_legacy-cpp20
|
|
||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
|
||||||
)
|
|
||||||
|
|
||||||
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
||||||
json_test_set_test_options(test-class_parser_diagnostic_positions
|
json_test_set_test_options(test-class_parser_diagnostic_positions
|
||||||
@@ -406,6 +373,25 @@ json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# the lexer again with the portable string scanning instead of SSE2/NEON, and on
|
||||||
|
# x86-64 with SSSE3 compiled in (no check of the CPU)
|
||||||
|
json_test_set_test_options(test-class_lexer_no_simd
|
||||||
|
COMPILE_DEFINITIONS JSON_NO_SIMD
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-class_lexer.cpp
|
||||||
|
NAME test-class_lexer_no_simd
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND NOT MSVC)
|
||||||
|
json_test_set_test_options(test-class_lexer_ssse3
|
||||||
|
COMPILE_OPTIONS -mssse3
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-class_lexer.cpp
|
||||||
|
NAME test-class_lexer_ssse3
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# test reading from a std::istream again without scanning the get area in bulk
|
# test reading from a std::istream again without scanning the get area in bulk
|
||||||
json_test_set_test_options(test-stream-bulk-scan_disabled
|
json_test_set_test_options(test-stream-bulk-scan_disabled
|
||||||
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
COMPILE_DEFINITIONS JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
# Unit tests
|
|
||||||
|
|
||||||
The unit tests are in [`src/unit-*.cpp`](src) and use [doctest](https://github.com/doctest/doctest). Each file becomes
|
|
||||||
one CTest test per C++ standard it is built for, named after the file: `src/unit-foo.cpp` becomes `test-foo_cpp11`.
|
|
||||||
|
|
||||||
## Build and run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cmake -S . -B build -DJSON_BuildTests=ON
|
|
||||||
cmake --build build -j 10
|
|
||||||
ctest --test-dir build -j 10
|
|
||||||
```
|
|
||||||
|
|
||||||
The [CMake options](../docs/mkdocs/docs/integration/cmake.md) starting with `JSON_Test` (and `JSON_FastTests`,
|
|
||||||
`JSON_Valgrind`) control the test build. The most relevant ones:
|
|
||||||
|
|
||||||
- `JSON_TestStandards`: build every test file for the given standards, e.g., `-DJSON_TestStandards=17`. By default,
|
|
||||||
a file is built for C++11 and for each standard whose `JSON_HAS_CPP_<N>` macro it mentions (see below).
|
|
||||||
- `JSON_TestUnityBuild`: compile several test files together (see below). Set it to `OFF` to get one executable per
|
|
||||||
test file, e.g., to debug a single test in a debugger.
|
|
||||||
- `JSON_TestShard`: build only a part of the test files, e.g., `-DJSON_TestShard=0/2`.
|
|
||||||
|
|
||||||
To run the tests of one file only, run its CTest test, e.g., `ctest --test-dir build -R test-foo_cpp11`.
|
|
||||||
|
|
||||||
## How test files are built
|
|
||||||
|
|
||||||
Two mechanisms keep the compile time down. Both are automatic, but they set a few rules for test files.
|
|
||||||
|
|
||||||
### C++ standards
|
|
||||||
|
|
||||||
A test file is built for a C++ standard beyond C++11 if its text contains `JSON_HAS_CPP_<N>`, for instance in
|
|
||||||
`#ifdef JSON_HAS_CPP_17`, or in a comment `// JSON_HAS_CPP_17` next to code that needs a macro only defined for that
|
|
||||||
standard, such as `JSON_HAS_FILESYSTEM`. Then the *whole file* is compiled again for that standard.
|
|
||||||
|
|
||||||
So that this does not happen for the large files, tests that need C++14, C++17, or C++20 go into a separate file
|
|
||||||
`src/unit-<name>-cpp<N>.cpp` (e.g., [`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp)), whose body
|
|
||||||
is wrapped in `#ifdef JSON_HAS_CPP_<N>`. The main file `src/unit-<name>.cpp` must not mention `JSON_HAS_CPP_<N>` at
|
|
||||||
all, not even in a comment, so it is built for C++11 only. The CI jobs `ci_test_*_cxx<N>` still build every test file
|
|
||||||
for every standard.
|
|
||||||
|
|
||||||
### Unity build
|
|
||||||
|
|
||||||
With `JSON_TestUnityBuild` (`ON` by default, `OFF` with MinGW), CMake compiles several test files as one translation
|
|
||||||
unit, so they share the template instantiations of the library. CMake generates `build/tests/unity/test-unity-*.cpp`,
|
|
||||||
which `#include` the test files of a batch. Every test file still gets its own CTest test, which runs the batch
|
|
||||||
executable with `--source-file=*unit-foo.cpp`, so only the test cases of that file run.
|
|
||||||
|
|
||||||
CMake decides from the macros a file defines *before* including `<nlohmann/json.hpp>`:
|
|
||||||
|
|
||||||
| Macros defined before the include | Built as |
|
|
||||||
|-------------------------------------------------------------------------------------------|-----------------------------------|
|
|
||||||
| none (or only `SKIP_TESTS_FOR_*` and similar macros derived from global definitions) | batch with the other plain files |
|
|
||||||
| only `JSON_TESTS_PRIVATE` | batch with the other such files |
|
|
||||||
| any library configuration macro (`JSON_DIAGNOSTICS`, `JSON_NO_IO`, `JSON_ASSERT`, ...) | its own executable |
|
|
||||||
|
|
||||||
Files with test-specific build options (`json_test_set_test_options(test-foo ...)` in
|
|
||||||
[`CMakeLists.txt`](CMakeLists.txt)) also get their own executable. The batchable files are split alphabetically into
|
|
||||||
batches of `JSON_TestUnityBatchSize` files, except for explicit groups of related files: `json_test_unity_group_binary`
|
|
||||||
compiles all binary-format tests together, because they share the binary readers and writers. Only add a group if it
|
|
||||||
measurably pays off; files that share little just make one slow translation unit.
|
|
||||||
|
|
||||||
## Rules for test files
|
|
||||||
|
|
||||||
Because the files of a batch share one translation unit, a test file must not affect the files that follow it:
|
|
||||||
|
|
||||||
1. **Give file-scope helpers file-specific names.** An anonymous namespace does not help, as the batch is one
|
|
||||||
translation unit. Use a name such as `my_allocator_2982`, or wrap the helpers in a namespace named after the file
|
|
||||||
(e.g., `namespace unit_comparison_detail`). Clashes show up as compile errors.
|
|
||||||
2. **Do not `#undef` library macros**, such as `JSON_HAS_CPP_17`, at the end of a file: this silently disables the tests
|
|
||||||
that depend on them in the later files of the batch. `#undef` macros you define yourself after including the library.
|
|
||||||
3. **Put version-dependent tests into `unit-<name>-cpp<N>.cpp`** (see [C++ standards](#c-standards)).
|
|
||||||
4. **Define test cases in the `.cpp` file itself.** The batch executable selects the tests of a file by its name, so a
|
|
||||||
`TEST_CASE` in a shared header is not run. The only exception is the test case in
|
|
||||||
[`src/make_test_data_available.hpp`](src/make_test_data_available.hpp), which CMake handles explicitly.
|
|
||||||
|
|
||||||
To check a new file for clashes with all others at once, build with `-DJSON_TestUnityBatchSize=1000`, which puts all
|
|
||||||
batchable files of a kind into one translation unit.
|
|
||||||
|
|
||||||
## Where to add tests
|
|
||||||
|
|
||||||
Tests are structured along the features of the library. Usually, an existing file is the right place:
|
|
||||||
|
|
||||||
- For a bug fix, add a section referencing the issue to [`src/unit-regression3.cpp`](src/unit-regression3.cpp), or to
|
|
||||||
[`src/unit-regression3-cpp17.cpp`](src/unit-regression3-cpp17.cpp) /
|
|
||||||
[`src/unit-regression3-cpp20.cpp`](src/unit-regression3-cpp20.cpp) if the test needs C++17 / C++20.
|
|
||||||
[`src/unit-regression2.cpp`](src/unit-regression2.cpp) holds older tests and should not grow further.
|
|
||||||
- When testing exceptions, use `CHECK_THROWS_WITH_AS`, which also checks the `what()` message.
|
|
||||||
|
|
||||||
A new file `src/unit-<name>.cpp` is picked up automatically when CMake runs again; no change to `CMakeLists.txt` is
|
|
||||||
needed.
|
|
||||||
|
|
||||||
See also [`fuzzing.md`](fuzzing.md) for fuzz testing.
|
|
||||||
@@ -24,6 +24,7 @@ foreach(test default noversion)
|
|||||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1
|
||||||
JSON_DISABLE_STREAM_BULK_SCAN=1
|
JSON_DISABLE_STREAM_BULK_SCAN=1
|
||||||
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
JSON_DISABLE_UNLOCKED_FILE_IO=1
|
||||||
|
JSON_NO_SIMD
|
||||||
JSON_VIEW_NO_SIMD
|
JSON_VIEW_NO_SIMD
|
||||||
JSON_VIEW_USE_SSSE3)
|
JSON_VIEW_USE_SSSE3)
|
||||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_dufio";
|
expected += "_dufio";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
expected += "_ns";
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
expected += "_vns";
|
expected += "_vns";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -61,6 +61,10 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_dufio";
|
expected += "_dufio";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_NO_SIMD
|
||||||
|
expected += "_ns";
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef JSON_VIEW_NO_SIMD
|
#ifdef JSON_VIEW_NO_SIMD
|
||||||
expected += "_vns";
|
expected += "_vns";
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
|||||||
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
| `ViewAccept` | validate with `json_document::accept`; compare with `Accept` |
|
||||||
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
| `ViewMaterialize` | convert a parsed `json_document` into a `json` value |
|
||||||
| `ViewDump` | serialize a parsed `json_document`; compare with `Dump` |
|
| `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`,
|
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`, ...).
|
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
build/
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# json_view compared with other libraries
|
||||||
|
|
||||||
|
The in-tree benchmarks in [`tests/benchmarks`](../README.md) measure `json_document` against `json::parse` only. The
|
||||||
|
programs here compare it with [yyjson](https://github.com/ibireme/yyjson),
|
||||||
|
[simdjson](https://github.com/simdjson/simdjson), and [Boost.JSON](https://github.com/boostorg/json): the question
|
||||||
|
users ask when they pick a library. They are not built by CMake or run by CI.
|
||||||
|
|
||||||
|
## Reproducing the numbers
|
||||||
|
|
||||||
|
`compare.py` builds the programs against `include/` of this checkout, runs them, and writes the results together with
|
||||||
|
everything needed to reproduce them to `results/<date>-<host>.md` (and `.csv`): the date, the commit, the CPU, the
|
||||||
|
OS, the compiler, the flags, and the versions of all libraries.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory> [--native] [--rounds 30]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `--data` is the downloaded [test data](https://github.com/nlohmann/json_test_data), e.g. the `test_files` directory
|
||||||
|
of a CMake build directory. It needs `nativejson-benchmark/{twitter,citm_catalog,canada}.json` and
|
||||||
|
`jeopardy/jeopardy.json`.
|
||||||
|
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||||
|
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||||
|
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||||
|
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||||
|
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||||
|
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||||
|
- Only the Python 3 standard library is used; a C++17 compiler is needed (`CXX` and `CC` are honored).
|
||||||
|
|
||||||
|
For numbers worth publishing, use a quiet machine (see [Getting stable numbers](../README.md#getting-stable-numbers)),
|
||||||
|
the default 30 rounds or more, and `--native` only if the other libraries were built for the same CPU.
|
||||||
|
|
||||||
|
### On GitHub-hosted runners
|
||||||
|
|
||||||
|
The workflow [json_view benchmarks](../../../.github/workflows/json_view_benchmarks.yml) runs `compare.py --download`
|
||||||
|
on demand: by hand (Actions → "json_view benchmarks" → "Run workflow"), on an x86-64 or AArch64 Ubuntu runner with GCC
|
||||||
|
or Clang, or when a pull request gets the label `benchmark`, on both architectures with GCC. The results appear as the
|
||||||
|
job summary and as an artifact. Shared runners are noisy, so these numbers show
|
||||||
|
where `json_view` stands on another architecture; they are not meant for publication.
|
||||||
|
|
||||||
|
## What is measured
|
||||||
|
|
||||||
|
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||||
|
JSON-RPC request (`rpc`):
|
||||||
|
|
||||||
|
| workload | what it does |
|
||||||
|
|---|---|
|
||||||
|
| parse | build and free a document |
|
||||||
|
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||||
|
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||||
|
| dump | serialize a parsed document (compact) |
|
||||||
|
|
||||||
|
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||||
|
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||||
|
yyjson with numbers read as raw text (`YYJSON_READ_NUMBER_AS_RAW`), without converting them.
|
||||||
|
|
||||||
|
`bench_edit.cpp` measures read-modify-write: parse, apply the same logical edits with each library's own API, and
|
||||||
|
serialize (compact). Workloads: `patch` (a handful of edits at fixed places) and `update` (edits in every record).
|
||||||
|
An editable `json_document` edits in place; yyjson copies its immutable document into a mutable one first
|
||||||
|
(`yyjson_doc_mut_copy`); Boost.JSON and `json::parse` build mutable DOMs; simdjson cannot edit a document. All
|
||||||
|
outputs are checked to describe the same value.
|
||||||
|
|
||||||
|
Before anything is timed, all engines must accept each document and agree on the traversal: the number of values, the
|
||||||
|
bytes of all strings and keys, and the sum of all numbers. All engines run interleaved in every round, and the best
|
||||||
|
round is reported, as time and as a factor of the `json_view` time (below 1 means faster than `json_view`). Each timed
|
||||||
|
call follows an untimed call of the same engine: otherwise the engine after `json::parse` pays for the allocator
|
||||||
|
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||||
|
slower on citm_catalog traverse).
|
||||||
|
|
||||||
|
The engines do not all offer the same features, which the numbers should be read with:
|
||||||
|
|
||||||
|
| engine | document | random access | editable | notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||||
|
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||||
|
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||||
|
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||||
|
| Boost.JSON | owning, mutable DOM | yes | yes | monotonic resource |
|
||||||
|
| `json::parse` | owning, mutable DOM | yes | yes | |
|
||||||
|
|
||||||
|
Reusing memory matters as much as the parser. simdjson DOM reuses its parser, so it writes into memory it already
|
||||||
|
touched; a fresh `json_view` document or yyjson document gets new memory for every parse. On Linux, glibc returns large
|
||||||
|
blocks to the system when they are freed, so every fresh parse of a large document pays a page fault per 4 KiB page:
|
||||||
|
on x86-64 Linux, a fresh `json_view` parse of jeopardy took about twice as long as a reused one. On macOS on Apple
|
||||||
|
silicon, with 16 KiB pages, the difference is much smaller. Compare "json_view (reused)" with "simdjson DOM", and the
|
||||||
|
fresh `json_view` with "simdjson DOM (fresh)" and yyjson.
|
||||||
|
|
||||||
|
## Published results
|
||||||
|
|
||||||
|
Results are only published with the file `compare.py` wrote, which names the machine and the versions; see
|
||||||
|
`results/`. Numbers from one machine and compiler do not carry over to another: rerun the script.
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Corpus benchmark: the read-only workloads of bench_view.cpp on any list of
|
||||||
|
// JSON files (for example the benchmark sets of simdjson and yyjson).
|
||||||
|
//
|
||||||
|
// ./bench_corpus [--rounds N] file...
|
||||||
|
//
|
||||||
|
// For every file, all engines must accept it and agree on a traversal (value
|
||||||
|
// count, string bytes, sum of numbers) before anything is timed. Workloads:
|
||||||
|
// parse (build and free a document), traverse (visit every value, convert
|
||||||
|
// every number), dump (compact), and for json_view also dump with the source
|
||||||
|
// number text, compared with yyjson writing numbers read as raw text
|
||||||
|
// (YYJSON_READ_NUMBER_AS_RAW). Results go to bench_corpus.csv.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
st.num += yyjson_is_sint(v) ? static_cast<double>(yyjson_get_sint(v)) : yyjson_is_uint(v) ? static_cast<double>(yyjson_get_uint(v)) : yyjson_get_real(v);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool same(const stats& a, const stats& b)
|
||||||
|
{
|
||||||
|
return a.nodes == b.nodes && a.str == b.str && (a.num == b.num || std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
int rounds = 0; // 0: by size
|
||||||
|
std::vector<std::string> files;
|
||||||
|
for (int i = 1; i < argc; ++i)
|
||||||
|
{
|
||||||
|
if (std::strcmp(argv[i], "--rounds") == 0 && i + 1 < argc)
|
||||||
|
{
|
||||||
|
// (clamped: 0 rounds by size, at most a million)
|
||||||
|
rounds = static_cast<int>((std::min)((std::max)(std::strtol(argv[++i], nullptr, 10), 0L), 1000000L));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
files.push_back(argv[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::FILE* csv = std::fopen("bench_corpus.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_corpus.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "file,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
for (const auto& path : files)
|
||||||
|
{
|
||||||
|
const std::string s = slurp(path);
|
||||||
|
const std::string name = path.substr(path.rfind('/') + 1);
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
|
||||||
|
// all engines must agree before timing
|
||||||
|
stats a, b, c, d;
|
||||||
|
const json_document doc = json_document::parse(s);
|
||||||
|
walk(doc.root(), a);
|
||||||
|
yyjson_doc* y = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
auto sjr = sj.parse(ps);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
boost::json::parse_options opt;
|
||||||
|
opt.numbers = boost::json::number_precision::precise;
|
||||||
|
boost::json::monotonic_resource mr0;
|
||||||
|
const boost::json::value bv = boost::json::parse(s, &mr0, opt);
|
||||||
|
#endif
|
||||||
|
if (y == nullptr || sjr.error())
|
||||||
|
{
|
||||||
|
std::printf("%-34s skipped (an engine rejects it)\n", name.c_str());
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
walk(yyjson_doc_get_root(y), b);
|
||||||
|
walk(sjr.value_unsafe(), c);
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
walk(bv, d);
|
||||||
|
#else
|
||||||
|
d = a;
|
||||||
|
#endif
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
const bool ok = same(a, b) && same(a, c) && same(a, d);
|
||||||
|
|
||||||
|
const int r = rounds > 0 ? rounds : static_cast<int>(std::max<std::size_t>(3, std::min<std::size_t>(60, 400000000 / (s.size() + 1))));
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
json_document vd = json_document::parse(s);
|
||||||
|
yyjson_doc* yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_doc* yd_raw = yyjson_read(s.data(), s.size(), YYJSON_READ_NUMBER_AS_RAW);
|
||||||
|
simdjson::dom::parser sjd;
|
||||||
|
const simdjson::dom::element se = sjd.parse(ps).value_unsafe();
|
||||||
|
const std::vector<std::pair<std::string, std::vector<engine>>> workloads =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"parse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); g_sink = static_cast<double>(x.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(x)); yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"traverse", {
|
||||||
|
{"json_view", [&] { auto x = json_document::parse(s); stats st; walk(x.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* x = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(x), st); g_sink = st.num; yyjson_doc_free(x); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json_view (source numbers)", [&] { std::string o = vd.root().dump(-1, ' ', false, json_view::number_format::source); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson (raw numbers)", [&] { std::size_t n = 0; char* o = yyjson_write(yd_raw, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
std::printf("%-34s %9zu B%s\n", name.c_str(), s.size(), ok ? "" : " [ENGINES DISAGREE]");
|
||||||
|
for (const auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
wl.second[k].fn();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
wl.second[k].fn();
|
||||||
|
best[k] = std::min(best[k], std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::printf(" %-9s", wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
std::printf(" %s %.2f GB/s (%.2fx)", wl.second[k].name.c_str(), static_cast<double>(s.size()) / best[k], best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
yyjson_doc_free(yd_raw);
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,589 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Read-modify-write benchmark: parse a document, apply the same logical edits
|
||||||
|
// with each library's own API, and serialize it (compact).
|
||||||
|
//
|
||||||
|
// json_view json_editable_document: edits in place, unchanged values stay in the index
|
||||||
|
// yyjson yyjson_read + yyjson_doc_mut_copy (the way to edit a parsed document)
|
||||||
|
// Boost.JSON parse into a mutable DOM (monotonic resource, precise numbers), serialize
|
||||||
|
// json::parse nlohmann::json today
|
||||||
|
// simdjson has no mutable document and is not part of this comparison.
|
||||||
|
//
|
||||||
|
// Workloads:
|
||||||
|
// patch a handful of edits at fixed places (scalars, a new member, a new array element)
|
||||||
|
// update edits in every record (twitter: 100 statuses, citm: 243 performances,
|
||||||
|
// canada: 480 rings, jeopardy: 216,930 questions): set scalars, erase a
|
||||||
|
// member, add a member (canada: replace the first point of every ring)
|
||||||
|
//
|
||||||
|
// Build: see README.md (same flags as bench_view.cpp).
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_editable_document;
|
||||||
|
using nlohmann::json_editable_view;
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
namespace bj = boost::json;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static volatile std::size_t g_sink;
|
||||||
|
|
||||||
|
// ---------------- json_view ----------------
|
||||||
|
|
||||||
|
static std::string edit_view(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json_editable_document d = json_editable_document::parse(s);
|
||||||
|
const json_editable_view r = d.root();
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (const json_editable_view st : r["statuses"])
|
||||||
|
{
|
||||||
|
d.set(st, "retweet_count", i++);
|
||||||
|
d.set(st, "favorited", true);
|
||||||
|
d.set(st, "text", "redacted");
|
||||||
|
d.erase(st, "entities");
|
||||||
|
d.set(st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["search_metadata"], "count", 200);
|
||||||
|
d.set(r["statuses"][0], "text", "patched");
|
||||||
|
d.set(r["statuses"][0]["user"], "followers_count", 1);
|
||||||
|
d.set(r["statuses"][99], "favorited", true);
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view p : r["performances"])
|
||||||
|
{
|
||||||
|
d.set(p, "name", "performance");
|
||||||
|
d.set(p, "start", p["start"].get<std::int64_t>() + 1);
|
||||||
|
d.erase(p, "seatMapImage");
|
||||||
|
d.set(p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["events"]["138586341"], "name", "patched");
|
||||||
|
d.set(r["performances"][0], "start", 0);
|
||||||
|
d.set(r["venueNames"], "PLEYEL_PLEYEL", "Salle");
|
||||||
|
d.set(r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_editable_view coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view ring : coords)
|
||||||
|
{
|
||||||
|
d.set(ring, 0, json::array({0.5, 0.5}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r["features"][0]["properties"], "name", "patched");
|
||||||
|
d.set(r, "type", "FeatureCollection2");
|
||||||
|
d.set(coords[0], 0, json::array({0.0, 0.0}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (const json_editable_view q : r)
|
||||||
|
{
|
||||||
|
d.set(q, "value", "$1");
|
||||||
|
d.erase(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
d.set(r[0], "value", "$0");
|
||||||
|
d.set(r[100000], "answer", "patched");
|
||||||
|
d.set(r[216929], "round", "x");
|
||||||
|
d.push_back(r, json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
d.set(r, "retweet_count", 1);
|
||||||
|
d.set(r["user"], "name", "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
d.set(r, "id", 4);
|
||||||
|
d.set(r["params"], "subtrahend", 24);
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- nlohmann::json ----------------
|
||||||
|
|
||||||
|
static std::string edit_json(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
json r = json::parse(s);
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& st : r["statuses"])
|
||||||
|
{
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["search_metadata"]["count"] = 200;
|
||||||
|
r["statuses"][0]["text"] = "patched";
|
||||||
|
r["statuses"][0]["user"]["followers_count"] = 1;
|
||||||
|
r["statuses"][99]["favorited"] = true;
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].get<std::int64_t>() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["events"]["138586341"]["name"] = "patched";
|
||||||
|
r["performances"][0]["start"] = 0;
|
||||||
|
r["venueNames"]["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
r["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
json& coords = r["features"][0]["geometry"]["coordinates"];
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring[0] = json::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r["features"][0]["properties"]["name"] = "patched";
|
||||||
|
r["type"] = "FeatureCollection2";
|
||||||
|
coords[0][0] = json::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& q : r)
|
||||||
|
{
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
r[0]["value"] = "$0";
|
||||||
|
r[100000]["answer"] = "patched";
|
||||||
|
r[216929]["round"] = "x";
|
||||||
|
r.push_back(json::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
r["retweet_count"] = 1;
|
||||||
|
r["user"]["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
r["id"] = 4;
|
||||||
|
r["params"]["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return r.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- yyjson ----------------
|
||||||
|
|
||||||
|
static std::string edit_yyjson(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
yyjson_doc* idoc = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
yyjson_mut_doc* d = yyjson_doc_mut_copy(idoc, nullptr);
|
||||||
|
yyjson_doc_free(idoc);
|
||||||
|
yyjson_mut_val* r = yyjson_mut_doc_get_root(d);
|
||||||
|
auto get = [](yyjson_mut_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_mut_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* sts = get(r, "statuses");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* st;
|
||||||
|
std::int64_t i = 0;
|
||||||
|
yyjson_mut_arr_foreach(sts, idx, max, st)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(st, "retweet_count"), i++);
|
||||||
|
yyjson_mut_set_bool(get(st, "favorited"), true);
|
||||||
|
yyjson_mut_set_str(get(st, "text"), "redacted");
|
||||||
|
yyjson_mut_obj_remove_key(st, "entities");
|
||||||
|
yyjson_mut_obj_add_bool(d, st, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(get(r, "search_metadata"), "count"), 200);
|
||||||
|
yyjson_mut_val* s0 = yyjson_mut_arr_get(sts, 0);
|
||||||
|
yyjson_mut_set_str(get(s0, "text"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(get(s0, "user"), "followers_count"), 1);
|
||||||
|
yyjson_mut_set_bool(get(yyjson_mut_arr_get(sts, 99), "favorited"), true);
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* p;
|
||||||
|
yyjson_mut_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(p, "name"), "performance");
|
||||||
|
yyjson_mut_val* start = get(p, "start");
|
||||||
|
yyjson_mut_set_sint(start, yyjson_mut_get_sint(start) + 1);
|
||||||
|
yyjson_mut_obj_remove_key(p, "seatMapImage");
|
||||||
|
yyjson_mut_obj_add_bool(d, p, "edited", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(get(get(r, "events"), "138586341"), "name"), "patched");
|
||||||
|
yyjson_mut_set_sint(get(yyjson_mut_arr_get(get(r, "performances"), 0), "start"), 0);
|
||||||
|
yyjson_mut_set_str(get(get(r, "venueNames"), "PLEYEL_PLEYEL"), "Salle");
|
||||||
|
yyjson_mut_obj_add_bool(d, r, "patched", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_mut_val* f0 = yyjson_mut_arr_get(get(r, "features"), 0);
|
||||||
|
yyjson_mut_val* coords = get(get(f0, "geometry"), "coordinates");
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
static const double half[2] = {0.5, 0.5};
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* ring;
|
||||||
|
yyjson_mut_arr_foreach(coords, idx, max, ring)
|
||||||
|
{
|
||||||
|
yyjson_mut_arr_replace(ring, 0, yyjson_mut_arr_with_real(d, half, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static const double zero[2] = {0.0, 0.0};
|
||||||
|
yyjson_mut_set_str(get(get(f0, "properties"), "name"), "patched");
|
||||||
|
yyjson_mut_set_str(get(r, "type"), "FeatureCollection2");
|
||||||
|
yyjson_mut_arr_replace(yyjson_mut_arr_get(coords, 0), 0, yyjson_mut_arr_with_real(d, zero, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_mut_val* q;
|
||||||
|
yyjson_mut_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(q, "value"), "$1");
|
||||||
|
yyjson_mut_obj_remove_key(q, "air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 0), "value"), "$0");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 100000), "answer"), "patched");
|
||||||
|
yyjson_mut_set_str(get(yyjson_mut_arr_get(r, 216929), "round"), "x");
|
||||||
|
yyjson_mut_val* o = yyjson_mut_obj(d);
|
||||||
|
yyjson_mut_obj_add_str(d, o, "category", "NEW");
|
||||||
|
yyjson_mut_obj_add_str(d, o, "value", "$5");
|
||||||
|
yyjson_mut_arr_append(r, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "retweet_count"), 1);
|
||||||
|
yyjson_mut_set_str(get(get(r, "user"), "name"), "x");
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
yyjson_mut_set_sint(get(r, "id"), 4);
|
||||||
|
yyjson_mut_set_sint(get(get(r, "params"), "subtrahend"), 24);
|
||||||
|
}
|
||||||
|
std::size_t n = 0;
|
||||||
|
char* out = yyjson_mut_write(d, 0, &n);
|
||||||
|
std::string result(out, n);
|
||||||
|
std::free(out);
|
||||||
|
yyjson_mut_doc_free(d);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
// ---------------- Boost.JSON ----------------
|
||||||
|
|
||||||
|
static std::string edit_boost(const std::string& name, const std::string& s, bool update)
|
||||||
|
{
|
||||||
|
bj::monotonic_resource mr;
|
||||||
|
bj::parse_options opt;
|
||||||
|
opt.numbers = bj::number_precision::precise; // correctly rounded, like the others
|
||||||
|
bj::value v = bj::parse(s, &mr, opt);
|
||||||
|
bj::object* const obj = v.if_object(); // nullptr for jeopardy (an array)
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
bj::array& sts = (*obj)["statuses"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
std::int64_t i = 0;
|
||||||
|
for (auto& e : sts)
|
||||||
|
{
|
||||||
|
bj::object& st = e.as_object();
|
||||||
|
st["retweet_count"] = i++;
|
||||||
|
st["favorited"] = true;
|
||||||
|
st["text"] = "redacted";
|
||||||
|
st.erase("entities");
|
||||||
|
st["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["search_metadata"].as_object()["count"] = 200;
|
||||||
|
bj::object& s0 = sts[0].as_object();
|
||||||
|
s0["text"] = "patched";
|
||||||
|
s0["user"].as_object()["followers_count"] = 1;
|
||||||
|
sts[99].as_object()["favorited"] = true;
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : (*obj)["performances"].as_array())
|
||||||
|
{
|
||||||
|
bj::object& p = e.as_object();
|
||||||
|
p["name"] = "performance";
|
||||||
|
p["start"] = p["start"].as_int64() + 1;
|
||||||
|
p.erase("seatMapImage");
|
||||||
|
p["edited"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*obj)["events"].as_object()["138586341"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["performances"].as_array()[0].as_object()["start"] = 0;
|
||||||
|
(*obj)["venueNames"].as_object()["PLEYEL_PLEYEL"] = "Salle";
|
||||||
|
(*obj)["patched"] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
bj::object& f0 = (*obj)["features"].as_array()[0].as_object();
|
||||||
|
bj::array& coords = f0["geometry"].as_object()["coordinates"].as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& ring : coords)
|
||||||
|
{
|
||||||
|
ring.as_array()[0] = bj::array({0.5, 0.5});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
f0["properties"].as_object()["name"] = "patched";
|
||||||
|
(*obj)["type"] = "FeatureCollection2";
|
||||||
|
coords[0].as_array()[0] = bj::array({0.0, 0.0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
bj::array& a = v.as_array();
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
for (auto& e : a)
|
||||||
|
{
|
||||||
|
bj::object& q = e.as_object();
|
||||||
|
q["value"] = "$1";
|
||||||
|
q.erase("air_date");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
a[0].as_object()["value"] = "$0";
|
||||||
|
a[100000].as_object()["answer"] = "patched";
|
||||||
|
a[216929].as_object()["round"] = "x";
|
||||||
|
a.push_back(bj::object({{"category", "NEW"}, {"value", "$5"}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "status")
|
||||||
|
{
|
||||||
|
(*obj)["retweet_count"] = 1;
|
||||||
|
(*obj)["user"].as_object()["name"] = "x";
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
(*obj)["id"] = 4;
|
||||||
|
(*obj)["params"].as_object()["subtrahend"] = 24;
|
||||||
|
}
|
||||||
|
return bj::serialize(v);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
// (clamped to 1 to a million)
|
||||||
|
const int rounds = argc > 2 ? static_cast<int>((std::min)((std::max)(std::strtol(argv[2], nullptr, 10), 1L), 1000000L)) : 20;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
using fn = std::string (*)(const std::string&, const std::string&, bool);
|
||||||
|
const std::vector<std::pair<std::string, fn>> engines =
|
||||||
|
{
|
||||||
|
{"json_view", edit_view}, {"yyjson", edit_yyjson},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", edit_boost},
|
||||||
|
#endif
|
||||||
|
{"json::parse", edit_json}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_edit.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_edit.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const bool update :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (update && (dc.name == "status" || dc.name == "rpc"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// all engines must produce the same value
|
||||||
|
const json expected = json::parse(edit_json(dc.name, dc.text, update));
|
||||||
|
bool ok = true;
|
||||||
|
for (const auto& e : engines)
|
||||||
|
{
|
||||||
|
ok = ok && json::parse(e.second(dc.name, dc.text, update)) == expected;
|
||||||
|
}
|
||||||
|
std::vector<double> best(engines.size(), 1e300);
|
||||||
|
const int r = dc.text.size() > 10000000 ? std::max(3, rounds / 4) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
g_sink = engines[k].second(dc.name, dc.text, update).size();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char* wl = update ? "update" : "patch";
|
||||||
|
std::printf("%-13s %-7s %s", dc.name.c_str(), wl, ok ? "" : "[OUTPUT MISMATCH] ");
|
||||||
|
for (std::size_t k = 0; k < engines.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %.*fus (%.2fx)", engines[k].first.c_str(), us < 10 ? 3 : (us < 1000 ? 1 : 0), us, best[k] / best[0]);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), dc.text.size(), wl, engines[k].first.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
@@ -0,0 +1,755 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||||
|
//
|
||||||
|
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
||||||
|
// yyjson yyjson_read(): immutable document, random access
|
||||||
|
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
||||||
|
// per parse): immutable, random access
|
||||||
|
// references (different feature sets):
|
||||||
|
// simdjson OD On-Demand: forward-only, lazy
|
||||||
|
// Boost.JSON owning, mutable DOM (monotonic resource)
|
||||||
|
// json::parse owning, mutable DOM (nlohmann today)
|
||||||
|
//
|
||||||
|
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||||
|
// number, touch every string and key), select (a few fields per document),
|
||||||
|
// dump (compact serialization of the parsed document).
|
||||||
|
// All engines run interleaved in every round, each timed call after an untimed
|
||||||
|
// one of the same engine; the best round is reported.
|
||||||
|
#include <nlohmann/json_view.hpp>
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
#include <boost/json.hpp>
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
#endif
|
||||||
|
#include <simdjson.h>
|
||||||
|
#include <yyjson.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
using nlohmann::json;
|
||||||
|
using nlohmann::json_document;
|
||||||
|
using nlohmann::json_view;
|
||||||
|
|
||||||
|
static volatile double g_sink;
|
||||||
|
|
||||||
|
struct stats
|
||||||
|
{
|
||||||
|
double num = 0;
|
||||||
|
std::size_t str = 0, nodes = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------- traversal ----------------
|
||||||
|
|
||||||
|
static void walk(json_view v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (auto it = v.begin(); it != v.end(); ++it)
|
||||||
|
{
|
||||||
|
st.str += it.key().size();
|
||||||
|
walk(*it, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const json_view e : v)
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += v.get<double>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(const json& j, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (j.type())
|
||||||
|
{
|
||||||
|
case json::value_t::object:
|
||||||
|
for (const auto& kv : j.get_ref<const json::object_t&>())
|
||||||
|
{
|
||||||
|
st.str += kv.first.size();
|
||||||
|
walk(kv.second, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::array:
|
||||||
|
for (const auto& e : j.get_ref<const json::array_t&>())
|
||||||
|
{
|
||||||
|
walk(e, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case json::value_t::string:
|
||||||
|
st.str += j.get_ref<const std::string&>().size();
|
||||||
|
break;
|
||||||
|
case json::value_t::number_integer:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_integer_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_unsigned:
|
||||||
|
st.num += static_cast<double>(*j.get_ptr<const json::number_unsigned_t*>());
|
||||||
|
break;
|
||||||
|
case json::value_t::number_float:
|
||||||
|
st.num += *j.get_ptr<const json::number_float_t*>();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(yyjson_val* v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (yyjson_get_type(v))
|
||||||
|
{
|
||||||
|
case YYJSON_TYPE_OBJ:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* k, * val;
|
||||||
|
yyjson_obj_foreach(v, idx, max, k, val)
|
||||||
|
{
|
||||||
|
st.str += yyjson_get_len(k);
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_ARR:
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* val;
|
||||||
|
yyjson_arr_foreach(v, idx, max, val)
|
||||||
|
{
|
||||||
|
walk(val, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case YYJSON_TYPE_STR:
|
||||||
|
st.str += yyjson_get_len(v);
|
||||||
|
break;
|
||||||
|
case YYJSON_TYPE_NUM:
|
||||||
|
if (yyjson_is_sint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_sint(v));
|
||||||
|
}
|
||||||
|
else if (yyjson_is_uint(v))
|
||||||
|
{
|
||||||
|
st.num += static_cast<double>(yyjson_get_uint(v));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
st.num += yyjson_get_real(v);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk(simdjson::dom::element e, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (e.type())
|
||||||
|
{
|
||||||
|
case simdjson::dom::element_type::OBJECT:
|
||||||
|
for (auto f : simdjson::dom::object(e))
|
||||||
|
{
|
||||||
|
st.str += f.key.size();
|
||||||
|
walk(f.value, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::ARRAY:
|
||||||
|
for (auto c : simdjson::dom::array(e))
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::STRING:
|
||||||
|
st.str += std::string_view(e).size();
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::INT64:
|
||||||
|
st.num += static_cast<double>(int64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::UINT64:
|
||||||
|
st.num += static_cast<double>(uint64_t(e));
|
||||||
|
break;
|
||||||
|
case simdjson::dom::element_type::DOUBLE:
|
||||||
|
st.num += double(e);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void walk_od(simdjson::ondemand::value v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::json_type::object:
|
||||||
|
for (auto f : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += std::string_view(f.unescaped_key()).size();
|
||||||
|
walk_od(f.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::array:
|
||||||
|
for (auto c : v.get_array())
|
||||||
|
{
|
||||||
|
walk_od(c.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::string:
|
||||||
|
st.str += std::string_view(v.get_string()).size();
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::json_type::number:
|
||||||
|
{
|
||||||
|
simdjson::ondemand::number n = v.get_number();
|
||||||
|
switch (n.get_number_type())
|
||||||
|
{
|
||||||
|
case simdjson::ondemand::number_type::signed_integer:
|
||||||
|
st.num += static_cast<double>(n.get_int64());
|
||||||
|
break;
|
||||||
|
case simdjson::ondemand::number_type::unsigned_integer:
|
||||||
|
st.num += static_cast<double>(n.get_uint64());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
st.num += n.get_double();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case simdjson::ondemand::json_type::boolean:
|
||||||
|
(void)bool(v.get_bool());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
(void)v.is_null();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
static void walk(const boost::json::value& v, stats& st)
|
||||||
|
{
|
||||||
|
++st.nodes;
|
||||||
|
switch (v.kind())
|
||||||
|
{
|
||||||
|
case boost::json::kind::object:
|
||||||
|
for (const auto& kv : v.get_object())
|
||||||
|
{
|
||||||
|
st.str += kv.key().size();
|
||||||
|
walk(kv.value(), st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::array:
|
||||||
|
for (const auto& c : v.get_array())
|
||||||
|
{
|
||||||
|
walk(c, st);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case boost::json::kind::string:
|
||||||
|
st.str += v.get_string().size();
|
||||||
|
break;
|
||||||
|
case boost::json::kind::int64:
|
||||||
|
st.num += static_cast<double>(v.get_int64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::uint64:
|
||||||
|
st.num += static_cast<double>(v.get_uint64());
|
||||||
|
break;
|
||||||
|
case boost::json::kind::double_:
|
||||||
|
st.num += v.get_double();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------- selective access ----------------
|
||||||
|
// twitter: per status id, user.screen_name, retweet_count
|
||||||
|
// citm: per performance id, eventId, #seatCategories; #events
|
||||||
|
// canada: type, features[0].geometry.type, #coordinates
|
||||||
|
// jeopardy: per question: round == "Final Jeopardy!", len(category)
|
||||||
|
// status (one tweet): id, user.screen_name, retweet_count
|
||||||
|
// rpc: method, params.minuend, id
|
||||||
|
|
||||||
|
static double pick(const std::string& name, json_view r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](json_view s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const json_view s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const json_view p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json_view g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const json_view q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_string().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_string().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, const json& r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](const json & s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||||
|
acc += static_cast<double>(s["user"]["screen_name"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (const auto& s : r["statuses"])
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (const auto& p : r["performances"])
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(r["events"].size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
const json& g = r["features"][0]["geometry"];
|
||||||
|
acc += static_cast<double>(r["type"].get_ref<const std::string&>().size() + g["type"].get_ref<const std::string&>().size() + g["coordinates"].size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (const auto& q : r)
|
||||||
|
{
|
||||||
|
acc += q["round"].get_ref<const std::string&>() == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(q["category"].get_ref<const std::string&>().size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(r["method"].get_ref<const std::string&>().size());
|
||||||
|
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, yyjson_val* r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
auto get = [](yyjson_val * o, const char* k)
|
||||||
|
{
|
||||||
|
return yyjson_obj_get(o, k);
|
||||||
|
};
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](yyjson_val * s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(s, "id")));
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(get(s, "user"), "screen_name")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(s, "retweet_count")));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* s;
|
||||||
|
yyjson_arr_foreach(get(r, "statuses"), idx, max, s)
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* p;
|
||||||
|
yyjson_arr_foreach(get(r, "performances"), idx, max, p)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_uint(get(p, "id")) + yyjson_get_uint(get(p, "eventId")) + yyjson_arr_size(get(p, "seatCategories")));
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(yyjson_obj_size(get(r, "events")));
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
yyjson_val* g = get(yyjson_arr_get(get(r, "features"), 0), "geometry");
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "type")) + yyjson_get_len(get(g, "type")) + yyjson_arr_size(get(g, "coordinates")));
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
std::size_t idx, max;
|
||||||
|
yyjson_val* q;
|
||||||
|
yyjson_arr_foreach(r, idx, max, q)
|
||||||
|
{
|
||||||
|
acc += yyjson_equals_str(get(q, "round"), "Final Jeopardy!") ? 1 : 0;
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(q, "category")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(yyjson_get_len(get(r, "method")));
|
||||||
|
acc += static_cast<double>(yyjson_get_sint(get(get(r, "params"), "minuend")) + yyjson_get_sint(get(r, "id")));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick(const std::string& name, simdjson::dom::element r)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::dom::element s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : simdjson::dom::array(r["statuses"]))
|
||||||
|
{
|
||||||
|
one(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
for (auto p : simdjson::dom::array(r["performances"]))
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(p["id"]) + uint64_t(p["eventId"]) + simdjson::dom::array(p["seatCategories"]).size());
|
||||||
|
}
|
||||||
|
acc += static_cast<double>(simdjson::dom::object(r["events"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
auto g = r["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(r["type"]).size() + std::string_view(g["type"]).size() + simdjson::dom::array(g["coordinates"]).size());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : simdjson::dom::array(r))
|
||||||
|
{
|
||||||
|
acc += std::string_view(q["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
acc += static_cast<double>(std::string_view(q["category"]).size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(r["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(r["params"]["minuend"]) + int64_t(r["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double pick_od(const std::string& name, simdjson::ondemand::document& d)
|
||||||
|
{
|
||||||
|
double acc = 0;
|
||||||
|
if (name == "twitter" || name == "status")
|
||||||
|
{
|
||||||
|
auto one = [&](simdjson::ondemand::object s)
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(uint64_t(s["id"]));
|
||||||
|
acc += static_cast<double>(std::string_view(s["user"]["screen_name"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(s["retweet_count"]));
|
||||||
|
};
|
||||||
|
if (name == "twitter")
|
||||||
|
{
|
||||||
|
for (auto s : d["statuses"])
|
||||||
|
{
|
||||||
|
one(s.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
one(d.get_object());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "citm_catalog")
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object ev = d["events"].get_object();
|
||||||
|
acc += static_cast<double>(ev.count_fields());
|
||||||
|
for (auto p : d["performances"])
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = p.get_object();
|
||||||
|
const auto a = uint64_t(o["eventId"]) + uint64_t(o["id"]);
|
||||||
|
simdjson::ondemand::array sc = o["seatCategories"].get_array();
|
||||||
|
acc += static_cast<double>(a + sc.count_elements());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "canada")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["type"]).size());
|
||||||
|
auto g = d["features"].at(0)["geometry"];
|
||||||
|
acc += static_cast<double>(std::string_view(g["type"]).size());
|
||||||
|
simdjson::ondemand::array co = g["coordinates"].get_array();
|
||||||
|
acc += static_cast<double>(co.count_elements());
|
||||||
|
}
|
||||||
|
else if (name == "jeopardy")
|
||||||
|
{
|
||||||
|
for (auto q : d)
|
||||||
|
{
|
||||||
|
simdjson::ondemand::object o = q.get_object();
|
||||||
|
acc += static_cast<double>(std::string_view(o["category"]).size());
|
||||||
|
acc += std::string_view(o["round"]) == "Final Jeopardy!" ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name == "rpc")
|
||||||
|
{
|
||||||
|
acc += static_cast<double>(std::string_view(d["method"]).size());
|
||||||
|
acc += static_cast<double>(int64_t(d["params"]["minuend"]));
|
||||||
|
acc += static_cast<double>(int64_t(d["id"]));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------- harness ----------------
|
||||||
|
|
||||||
|
static std::string slurp(const std::string& p)
|
||||||
|
{
|
||||||
|
std::ifstream f(p, std::ios::binary);
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "cannot open %s\n", p.c_str());
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << f.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct engine
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::function<void()> fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc < 2)
|
||||||
|
{
|
||||||
|
std::fprintf(stderr, "usage: %s <json_test_data directory> [rounds] [document]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const std::string T = std::string(argv[1]) + "/";
|
||||||
|
// (clamped to 1 to a million)
|
||||||
|
const int rounds = argc > 2 ? static_cast<int>((std::min)((std::max)(std::strtol(argv[2], nullptr, 10), 1L), 1000000L)) : 30;
|
||||||
|
const std::string only = argc > 3 ? argv[3] : "";
|
||||||
|
struct doc
|
||||||
|
{
|
||||||
|
std::string name, text;
|
||||||
|
int batch;
|
||||||
|
};
|
||||||
|
std::vector<doc> docs;
|
||||||
|
for (const char* f :
|
||||||
|
{"nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string n = std::string(f).substr(std::string(f).find('/') + 1);
|
||||||
|
docs.push_back({n.substr(0, n.size() - 5), slurp(T + f), 1});
|
||||||
|
}
|
||||||
|
docs.push_back({"status", json::parse(docs[0].text)["statuses"][0].dump(), 200});
|
||||||
|
docs.push_back({"rpc", R"({"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 3})", 5000});
|
||||||
|
|
||||||
|
// correctness cross-check of the workloads
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
stats a, b, c, dd;
|
||||||
|
walk(json::parse(dc.text), a);
|
||||||
|
auto d = json_document::parse(dc.text);
|
||||||
|
walk(d.root(), b);
|
||||||
|
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||||
|
walk(yyjson_doc_get_root(y), c);
|
||||||
|
simdjson::dom::parser p;
|
||||||
|
walk(p.parse(dc.text).value(), dd);
|
||||||
|
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||||
|
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||||
|
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||||
|
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||||
|
yyjson_doc_free(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||||
|
if (csv == nullptr)
|
||||||
|
{
|
||||||
|
std::perror("bench_view.csv");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::fprintf(csv, "doc,bytes,workload,engine,ns\n");
|
||||||
|
json_document reused;
|
||||||
|
simdjson::dom::parser sj;
|
||||||
|
simdjson::ondemand::parser od;
|
||||||
|
for (const auto& dc : docs)
|
||||||
|
{
|
||||||
|
if (!only.empty() && dc.name != only)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string& s = dc.text;
|
||||||
|
const simdjson::padded_string ps(s);
|
||||||
|
const std::string name = dc.name;
|
||||||
|
std::vector<std::pair<std::string, std::vector<engine>>> workloads;
|
||||||
|
|
||||||
|
workloads.push_back({"parse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); g_sink = v.is_object(); }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = static_cast<double>(j.size()); }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"traverse", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||||
|
#if JSON_VIEW_BENCH_BOOST
|
||||||
|
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); stats st; walk(v, st); g_sink = st.num; }},
|
||||||
|
#endif
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); stats st; walk(j, st); g_sink = st.num; }},
|
||||||
|
}});
|
||||||
|
workloads.push_back({"select", {
|
||||||
|
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||||
|
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||||
|
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||||
|
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||||
|
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||||
|
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||||
|
}});
|
||||||
|
{
|
||||||
|
// serialization of an already parsed document
|
||||||
|
static json_document vd;
|
||||||
|
vd.read(s);
|
||||||
|
static yyjson_doc* yd = nullptr;
|
||||||
|
if (yd)
|
||||||
|
{
|
||||||
|
yyjson_doc_free(yd);
|
||||||
|
}
|
||||||
|
yd = yyjson_read(s.data(), s.size(), 0);
|
||||||
|
static simdjson::dom::parser sjd;
|
||||||
|
static simdjson::dom::element se;
|
||||||
|
se = sjd.parse(ps).value_unsafe();
|
||||||
|
static json jd;
|
||||||
|
jd = json::parse(s);
|
||||||
|
workloads.push_back({"dump", {
|
||||||
|
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||||
|
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& wl : workloads)
|
||||||
|
{
|
||||||
|
std::vector<double> best(wl.second.size(), 1e300);
|
||||||
|
const int r = s.size() > 10000000 ? std::max(3, rounds / 5) : rounds;
|
||||||
|
for (int i = 0; i < r; ++i)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
// an untimed call first: whatever the previous engine left to the allocator
|
||||||
|
// (e.g. thousands of freed json nodes) is cleaned up here, not in the timing
|
||||||
|
wl.second[k].fn();
|
||||||
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
|
for (int b = 0; b < dc.batch; ++b)
|
||||||
|
{
|
||||||
|
wl.second[k].fn();
|
||||||
|
}
|
||||||
|
const double ns = std::chrono::duration<double, std::nano>(std::chrono::steady_clock::now() - t0).count() / dc.batch;
|
||||||
|
best[k] = std::min(best[k], ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const double ref = best[0];
|
||||||
|
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
||||||
|
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||||
|
{
|
||||||
|
const double us = best[k] / 1e3;
|
||||||
|
std::printf(" %s %s%s (%.2fx)", wl.second[k].name.c_str(), us >= 100 ? "" : "", (us >= 1000 ? std::to_string(static_cast<long>(us)) + "us" : (std::to_string(us).substr(0, 5) + "us")).c_str(), best[k] / ref);
|
||||||
|
std::fprintf(csv, "%s,%zu,%s,%s,%.1f\n", dc.name.c_str(), s.size(), wl.first.c_str(), wl.second[k].name.c_str(), best[k]);
|
||||||
|
}
|
||||||
|
std::printf("\n");
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fclose(csv);
|
||||||
|
}
|
||||||
Executable
+342
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# __ _____ _____ _____
|
||||||
|
# __| | __| | | | JSON for Modern C++ (supporting code)
|
||||||
|
# | | |__ | | | | | | version 3.12.0
|
||||||
|
# |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""Compare json_view with yyjson, simdjson, Boost.JSON, and json::parse.
|
||||||
|
|
||||||
|
Builds bench_view.cpp, bench_corpus.cpp, and bench_edit.cpp against the include/ directory of
|
||||||
|
this checkout, runs them, and writes the results with everything needed to
|
||||||
|
reproduce them (date, commit, CPU, OS, compiler, library versions, flags) to
|
||||||
|
results/<date>-<host>.md and .csv next to this script.
|
||||||
|
|
||||||
|
The other libraries come from the system (--system, the default: pkg-config
|
||||||
|
or Homebrew) or are downloaded as pinned releases and checked against their
|
||||||
|
SHA-256 (--download). Boost.JSON is optional: without Boost headers, its
|
||||||
|
columns are skipped, and the results say so.
|
||||||
|
|
||||||
|
Only the Python 3 standard library is used; a C++17 compiler is needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
|
# runs only the compilers and benchmark binaries this script builds
|
||||||
|
import subprocess # nosec B404
|
||||||
|
import sys
|
||||||
|
import tarfile
|
||||||
|
import tempfile
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
REPO = os.path.abspath(os.path.join(HERE, '..', '..', '..'))
|
||||||
|
|
||||||
|
# seconds without data after which a download fails
|
||||||
|
DOWNLOAD_TIMEOUT = 60
|
||||||
|
|
||||||
|
# pinned releases for --download; the hashes are those of the archives
|
||||||
|
#
|
||||||
|
# The yyjson and simdjson archives are the tag archives GitHub generates
|
||||||
|
# (archive/refs/tags/...), whose bytes GitHub does not guarantee to be stable. A SHA-256
|
||||||
|
# mismatch for one of them means that GitHub regenerated the archive: check the upstream
|
||||||
|
# tag, then pin the new hash.
|
||||||
|
PINNED = {
|
||||||
|
'yyjson': {
|
||||||
|
'version': '0.13.0',
|
||||||
|
'url': 'https://github.com/ibireme/yyjson/archive/refs/tags/0.13.0.tar.gz',
|
||||||
|
'sha256': '34e0f62a2bc11ab20d601e8ca1cc2b2079503aa45119a19133d89d19b94a0fae',
|
||||||
|
'dir': 'yyjson-0.13.0',
|
||||||
|
},
|
||||||
|
'simdjson': {
|
||||||
|
'version': '4.6.11',
|
||||||
|
'url': 'https://github.com/simdjson/simdjson/archive/refs/tags/v4.6.11.tar.gz',
|
||||||
|
'sha256': '61d948fc24f0d793829ad658058e7597d064988a89b4607ea02e401a82df98ff',
|
||||||
|
'dir': 'simdjson-4.6.11',
|
||||||
|
},
|
||||||
|
'boost': {
|
||||||
|
'version': '1.92.0',
|
||||||
|
'url': 'https://archives.boost.io/release/1.92.0/source/boost_1_92_0.tar.gz',
|
||||||
|
'sha256': 'c4a3b310ddd2472416e091067166b0713be97c63f38c212c484ada022fd296ce',
|
||||||
|
'dir': 'boost_1_92_0',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# the documents of bench_view.cpp, relative to the json_test_data directory
|
||||||
|
DEFAULT_CORPUS = [
|
||||||
|
'nativejson-benchmark/twitter.json',
|
||||||
|
'nativejson-benchmark/citm_catalog.json',
|
||||||
|
'nativejson-benchmark/canada.json',
|
||||||
|
'jeopardy/jeopardy.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
print('+ ' + ' '.join(shlex.quote(c) for c in cmd), flush=True)
|
||||||
|
# cmd is an argument list built by this script, never a shell string
|
||||||
|
return subprocess.run(cmd, check=True, **kwargs) # nosec B603
|
||||||
|
|
||||||
|
|
||||||
|
def output(cmd):
|
||||||
|
try:
|
||||||
|
# cmd is an argument list built by this script, never a shell string
|
||||||
|
return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout.strip() # nosec B603
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# libraries
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class Library:
|
||||||
|
"""include directories, sources to compile, and linker flags of a library"""
|
||||||
|
|
||||||
|
def __init__(self, name, include=None, sources=None, link=None, version=''):
|
||||||
|
self.name = name
|
||||||
|
self.include = include or []
|
||||||
|
self.sources = sources or []
|
||||||
|
self.link = link or []
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
def header_version(path, pattern):
|
||||||
|
try:
|
||||||
|
with open(path, encoding='utf-8', errors='replace') as f:
|
||||||
|
m = re.search(pattern, f.read())
|
||||||
|
return m.group(1) if m else ''
|
||||||
|
except OSError:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def library_version(name, include_dirs):
|
||||||
|
patterns = {
|
||||||
|
'yyjson': ('yyjson.h', r'#define\s+YYJSON_VERSION_STRING\s+"([^"]+)"'),
|
||||||
|
'simdjson': ('simdjson.h', r'#define\s+SIMDJSON_VERSION\s+"?([0-9.]+)"?'),
|
||||||
|
'boost': (os.path.join('boost', 'version.hpp'), r'#define\s+BOOST_LIB_VERSION\s+"([^"]+)"'),
|
||||||
|
}
|
||||||
|
header, pattern = patterns[name]
|
||||||
|
for d in include_dirs:
|
||||||
|
v = header_version(os.path.join(d, header), pattern)
|
||||||
|
if v:
|
||||||
|
return v.replace('_', '.')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def system_library(name):
|
||||||
|
"""a library found with pkg-config or Homebrew, or None"""
|
||||||
|
flags = output(['pkg-config', '--cflags', '--libs', name]).split()
|
||||||
|
if flags:
|
||||||
|
include = [f[2:] for f in flags if f.startswith('-I')]
|
||||||
|
link = [f for f in flags if f.startswith('-L') or f.startswith('-l')]
|
||||||
|
libdirs = [f[2:] for f in link if f.startswith('-L')]
|
||||||
|
link += ['-Wl,-rpath,' + d for d in libdirs]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
prefix = output(['brew', '--prefix', name]) if shutil.which('brew') else ''
|
||||||
|
if prefix and os.path.isdir(os.path.join(prefix, 'include')):
|
||||||
|
include = [os.path.join(prefix, 'include')]
|
||||||
|
link = []
|
||||||
|
if name != 'boost':
|
||||||
|
lib = os.path.join(prefix, 'lib')
|
||||||
|
link = ['-L' + lib, '-l' + name, '-Wl,-rpath,' + lib]
|
||||||
|
return Library(name, include, [], link, library_version(name, include))
|
||||||
|
if name == 'boost':
|
||||||
|
for d in ['/usr/include', '/usr/local/include']:
|
||||||
|
if os.path.isfile(os.path.join(d, 'boost', 'json.hpp')):
|
||||||
|
return Library(name, [d], [], [], library_version(name, [d]))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path):
|
||||||
|
"""SHA-256 of a file, read in chunks (the Boost archive is more than 100 MB)"""
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
for chunk in iter(lambda: f.read(1 << 20), b''):
|
||||||
|
h.update(chunk)
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def download_library(name, work):
|
||||||
|
"""a pinned release, downloaded and checked, or an error"""
|
||||||
|
pin = PINNED[name]
|
||||||
|
archive = os.path.join(work, 'download', os.path.basename(pin['url']))
|
||||||
|
os.makedirs(os.path.dirname(archive), exist_ok=True)
|
||||||
|
if not os.path.isfile(archive):
|
||||||
|
print(f'downloading {pin["url"]}', flush=True)
|
||||||
|
# the URLs are the https constants in PINNED, and the SHA-256 is checked below
|
||||||
|
# (into a .part file first, so that an interrupted download is not kept)
|
||||||
|
with urllib.request.urlopen(pin['url'], timeout=DOWNLOAD_TIMEOUT) as response: # nosec B310
|
||||||
|
with open(archive + '.part', 'wb') as f:
|
||||||
|
shutil.copyfileobj(response, f)
|
||||||
|
os.replace(archive + '.part', archive)
|
||||||
|
digest = sha256_file(archive)
|
||||||
|
if digest != pin['sha256']:
|
||||||
|
os.remove(archive) # downloaded again by the next run
|
||||||
|
sys.exit(f'error: SHA-256 of {archive} is {digest}, expected {pin["sha256"]} (removed)')
|
||||||
|
src = os.path.join(work, 'download', pin['dir'])
|
||||||
|
if not os.path.isdir(src):
|
||||||
|
# extracted into a temporary directory first, so that an interrupted extraction
|
||||||
|
# is not mistaken for a complete one
|
||||||
|
tmp = tempfile.mkdtemp(prefix=pin['dir'] + '.part-', dir=os.path.join(work, 'download'))
|
||||||
|
try:
|
||||||
|
with tarfile.open(archive) as t:
|
||||||
|
# (the 'data' filter rejects links and paths outside the target where Python has it)
|
||||||
|
kwargs = {'filter': 'data'} if hasattr(tarfile, 'data_filter') else {}
|
||||||
|
t.extractall(tmp, **kwargs) # noqa: S202 (checked archive) # nosec B202
|
||||||
|
os.rename(os.path.join(tmp, pin['dir']), src)
|
||||||
|
finally:
|
||||||
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
if name == 'yyjson':
|
||||||
|
return Library(name, [os.path.join(src, 'src')], [os.path.join(src, 'src', 'yyjson.c')], [], pin['version'])
|
||||||
|
if name == 'simdjson':
|
||||||
|
single = os.path.join(src, 'singleheader')
|
||||||
|
return Library(name, [single], [os.path.join(single, 'simdjson.cpp')], [], pin['version'])
|
||||||
|
return Library(name, [src], [], [], pin['version'])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# machine description
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def cpu_model():
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
return output(['sysctl', '-n', 'machdep.cpu.brand_string'])
|
||||||
|
try:
|
||||||
|
with open('/proc/cpuinfo', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('model name') or line.startswith('Model'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
# (AArch64 Linux: /proc/cpuinfo has no model name, lscpu knows it)
|
||||||
|
for line in output(['lscpu']).splitlines():
|
||||||
|
if line.startswith('Model name:'):
|
||||||
|
return line.split(':', 1)[1].strip()
|
||||||
|
return platform.processor() or platform.machine()
|
||||||
|
|
||||||
|
|
||||||
|
def git_commit():
|
||||||
|
commit = output(['git', '-C', REPO, 'rev-parse', '--short=12', 'HEAD'])
|
||||||
|
dirty = output(['git', '-C', REPO, 'status', '--porcelain', '--untracked-files=no'])
|
||||||
|
return commit + (' (with local changes)' if dirty else '')
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
ap.add_argument('--data', required=True, help='json_test_data directory (with nativejson-benchmark/ and jeopardy/)')
|
||||||
|
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||||
|
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||||
|
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||||
|
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||||
|
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||||
|
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||||
|
args = ap.parse_args()
|
||||||
|
# the benchmarks run in the build directory: make the paths absolute
|
||||||
|
args.data = os.path.abspath(args.data)
|
||||||
|
args.corpus = [os.path.abspath(f) for f in args.corpus]
|
||||||
|
args.build_dir = os.path.abspath(args.build_dir)
|
||||||
|
|
||||||
|
# (CXX and CC may contain arguments, e.g. 'ccache g++')
|
||||||
|
cxx = shlex.split(os.environ.get('CXX', 'c++'))
|
||||||
|
cc = shlex.split(os.environ.get('CC', 'cc'))
|
||||||
|
os.makedirs(args.build_dir, exist_ok=True)
|
||||||
|
|
||||||
|
libs = {}
|
||||||
|
for name in ['yyjson', 'simdjson', 'boost']:
|
||||||
|
if name == 'boost' and args.no_boost:
|
||||||
|
continue
|
||||||
|
lib = download_library(name, args.build_dir) if args.download else system_library(name)
|
||||||
|
if lib is None and name != 'boost':
|
||||||
|
sys.exit(f'error: {name} not found; install it, or use --download')
|
||||||
|
if lib is not None:
|
||||||
|
libs[name] = lib
|
||||||
|
with_boost = 'boost' in libs
|
||||||
|
if not with_boost:
|
||||||
|
print('Boost.JSON not found: its columns are skipped', flush=True)
|
||||||
|
|
||||||
|
flags = ['-std=c++17', '-O3', '-DNDEBUG', f'-DJSON_VIEW_BENCH_BOOST={1 if with_boost else 0}']
|
||||||
|
if args.native:
|
||||||
|
flags.append('-mcpu=native' if platform.machine().lower() in ('arm64', 'aarch64') else '-march=native')
|
||||||
|
include = ['-I' + os.path.join(REPO, 'include')] + ['-I' + d for lib in libs.values() for d in lib.include]
|
||||||
|
link = [f for lib in libs.values() for f in lib.link]
|
||||||
|
|
||||||
|
# C sources of downloaded libraries are compiled once
|
||||||
|
objects = []
|
||||||
|
for lib in libs.values():
|
||||||
|
for src in lib.sources:
|
||||||
|
obj = os.path.join(args.build_dir, os.path.basename(src) + '.o')
|
||||||
|
is_c = src.endswith('.c')
|
||||||
|
run((cc if is_c else cxx) + ([] if is_c else ['-std=c++17']) + ['-O3', '-DNDEBUG', '-c', src, '-o', obj]
|
||||||
|
+ ['-I' + d for d in lib.include])
|
||||||
|
objects.append(obj)
|
||||||
|
|
||||||
|
binaries = {}
|
||||||
|
for bench in ['bench_view', 'bench_corpus', 'bench_edit']:
|
||||||
|
exe = os.path.join(args.build_dir, bench)
|
||||||
|
run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||||
|
binaries[bench] = exe
|
||||||
|
|
||||||
|
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||||
|
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||||
|
outputs = {}
|
||||||
|
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||||
|
capture_output=True, text=True).stdout
|
||||||
|
for name, text in outputs.items():
|
||||||
|
print(text)
|
||||||
|
|
||||||
|
# results with their metadata
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||||
|
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||||
|
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||||
|
cxx_version = output(cxx + ['--version'])
|
||||||
|
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
||||||
|
meta = [
|
||||||
|
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||||
|
('commit', git_commit()),
|
||||||
|
('CPU', cpu_model()),
|
||||||
|
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||||
|
('compiler', compiler_version),
|
||||||
|
('flags', ' '.join(flags)),
|
||||||
|
('yyjson', libs['yyjson'].version),
|
||||||
|
('simdjson', libs['simdjson'].version),
|
||||||
|
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||||
|
('libraries from', 'pinned downloads' if args.download else 'the system'),
|
||||||
|
('rounds', str(args.rounds)),
|
||||||
|
]
|
||||||
|
with open(stem + '.md', 'w', encoding='utf-8') as f:
|
||||||
|
f.write(f'# json_view comparison, {now:%Y-%m-%d}\n\n')
|
||||||
|
f.write('Generated by `tests/benchmarks/json_view/compare.py`; best of the interleaved rounds.\n\n')
|
||||||
|
f.write('| | |\n|---|---|\n')
|
||||||
|
for key, value in meta:
|
||||||
|
f.write(f'| {key} | {value} |\n')
|
||||||
|
for name, text in outputs.items():
|
||||||
|
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
||||||
|
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||||
|
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||||
|
for name in ['bench_view', 'bench_corpus', 'bench_edit']:
|
||||||
|
path = os.path.join(args.build_dir, name + '.csv')
|
||||||
|
if os.path.isfile(path):
|
||||||
|
with open(path, encoding='utf-8') as f:
|
||||||
|
out.write(f'# {name}\n' + f.read())
|
||||||
|
print(f'results: {stem}.md, {stem}.csv')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -9,12 +9,15 @@
|
|||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <test_data.hpp>
|
#include <test_data.hpp>
|
||||||
|
|
||||||
@@ -710,6 +713,584 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
|||||||
|
|
||||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
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
|
// conversion of a JSON array into standard containers
|
||||||
template<typename Container>
|
template<typename Container>
|
||||||
static void GetArray(benchmark::State& state)
|
static void GetArray(benchmark::State& state)
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-allocator.cpp (std::string_view key
|
|
||||||
// lookup with a failing allocator). It is kept in a separate translation unit so the (much
|
|
||||||
// larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every C++
|
|
||||||
// standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <memory>
|
|
||||||
#include <new>
|
|
||||||
#include <string_view>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool next_construct_fails_cpp17 = false;
|
|
||||||
bool next_destroy_fails_cpp17 = false;
|
|
||||||
bool next_deallocate_fails_cpp17 = false;
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct my_allocator_cpp17 : std::allocator<T>
|
|
||||||
{
|
|
||||||
using std::allocator<T>::allocator;
|
|
||||||
|
|
||||||
template<class... Args>
|
|
||||||
void construct(T* p, Args&& ... args)
|
|
||||||
{
|
|
||||||
if (next_construct_fails_cpp17)
|
|
||||||
{
|
|
||||||
next_construct_fails_cpp17 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocate(T* p, std::size_t n)
|
|
||||||
{
|
|
||||||
if (next_deallocate_fails_cpp17)
|
|
||||||
{
|
|
||||||
next_deallocate_fails_cpp17 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::allocator<T>::deallocate(p, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy(T* p)
|
|
||||||
{
|
|
||||||
if (next_destroy_fails_cpp17)
|
|
||||||
{
|
|
||||||
next_destroy_fails_cpp17 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
|
||||||
p->~T();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class U>
|
|
||||||
struct rebind
|
|
||||||
{
|
|
||||||
using other = my_allocator_cpp17<U>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
|
||||||
// next_construct_fails_cpp17 set for the next allocation outside a check
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
TEST_CASE("a failed allocation leaves the value unchanged (C++17)")
|
|
||||||
{
|
|
||||||
// create JSON type using the throwing allocator
|
|
||||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp17>;
|
|
||||||
|
|
||||||
SECTION("turning a null value into an array or object")
|
|
||||||
{
|
|
||||||
my_json j;
|
|
||||||
|
|
||||||
next_construct_fails_cpp17 = true;
|
|
||||||
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
|
||||||
CHECK(j.is_null());
|
|
||||||
|
|
||||||
next_construct_fails_cpp17 = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++20-only part of unit-allocator.cpp (creating a value from a
|
|
||||||
// std::ranges view with a failing allocator). It is kept in a separate translation unit so
|
|
||||||
// the (much larger) unit-allocator.cpp is built for C++11 only and not rebuilt for every
|
|
||||||
// C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
#if JSON_HAS_RANGES
|
|
||||||
#include <ranges>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <new>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool next_construct_fails_cpp20 = false;
|
|
||||||
bool next_destroy_fails_cpp20 = false;
|
|
||||||
bool next_deallocate_fails_cpp20 = false;
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct my_allocator_cpp20 : std::allocator<T>
|
|
||||||
{
|
|
||||||
using std::allocator<T>::allocator;
|
|
||||||
|
|
||||||
template<class... Args>
|
|
||||||
void construct(T* p, Args&& ... args)
|
|
||||||
{
|
|
||||||
if (next_construct_fails_cpp20)
|
|
||||||
{
|
|
||||||
next_construct_fails_cpp20 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
::new (reinterpret_cast<void*>(p)) T(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocate(T* p, std::size_t n)
|
|
||||||
{
|
|
||||||
if (next_deallocate_fails_cpp20)
|
|
||||||
{
|
|
||||||
next_deallocate_fails_cpp20 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::allocator<T>::deallocate(p, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy(T* p)
|
|
||||||
{
|
|
||||||
if (next_destroy_fails_cpp20)
|
|
||||||
{
|
|
||||||
next_destroy_fails_cpp20 = false;
|
|
||||||
throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
static_cast<void>(p); // fix MSVC's C4100 warning
|
|
||||||
p->~T();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class U>
|
|
||||||
struct rebind
|
|
||||||
{
|
|
||||||
using other = my_allocator_cpp20<U>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// the no-exceptions CI job skips every CHECK_THROWS_AS, which would leave
|
|
||||||
// next_construct_fails_cpp20 set for the next allocation outside a check
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
TEST_CASE("a failed allocation leaves the value unchanged (C++20)")
|
|
||||||
{
|
|
||||||
// create JSON type using the throwing allocator
|
|
||||||
using my_json = nlohmann::json::with_allocator_t<my_allocator_cpp20>;
|
|
||||||
|
|
||||||
// With iterator debugging, VS 2015's containers construct a proxy with the
|
|
||||||
// allocator in constructors that cannot report its failure, so a failing
|
|
||||||
// allocator crashes this section there (SIGSEGV with VS 2015 Debug x86).
|
|
||||||
#if !(defined(_MSC_VER) && _MSC_VER < 1910 && defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
|
||||||
SECTION("converting into an existing value")
|
|
||||||
{
|
|
||||||
// to_json replaces the value it is given; the old one must survive a
|
|
||||||
// failed creation of the new one
|
|
||||||
my_json j = "old";
|
|
||||||
|
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
|
||||||
const std::vector<int> numbers = {1, 2};
|
|
||||||
next_construct_fails_cpp20 = true;
|
|
||||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
})), std::bad_alloc&);
|
|
||||||
CHECK(j == "old");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
next_construct_fails_cpp20 = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
#if JSON_HAS_RANGES
|
||||||
|
#include <ranges>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -681,6 +684,12 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
CHECK_THROWS_AS(j["key"], std::bad_alloc&);
|
||||||
CHECK(j.is_null());
|
CHECK(j.is_null());
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(j[std::string_view("key")], std::bad_alloc&);
|
||||||
|
CHECK(j.is_null());
|
||||||
|
#endif
|
||||||
|
|
||||||
next_construct_fails = true;
|
next_construct_fails = true;
|
||||||
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
CHECK_THROWS_AS(j.push_back(my_json(1)), std::bad_alloc&);
|
||||||
CHECK(j.is_null());
|
CHECK(j.is_null());
|
||||||
@@ -778,6 +787,16 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
|||||||
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
CHECK_THROWS_AS(nlohmann::to_json(j, std::valarray<int> {1, 2}), std::bad_alloc&);
|
||||||
CHECK(j == "old");
|
CHECK(j == "old");
|
||||||
|
|
||||||
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
|
const std::vector<int> numbers = {1, 2};
|
||||||
|
next_construct_fails = true;
|
||||||
|
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
})), std::bad_alloc&);
|
||||||
|
CHECK(j == "old");
|
||||||
|
#endif
|
||||||
|
|
||||||
next_construct_fails = false;
|
next_construct_fails = false;
|
||||||
nlohmann::to_json(j, std::vector<int> {1, 2});
|
nlohmann::to_json(j, std::vector<int> {1, 2});
|
||||||
CHECK(j == my_json({1, 2}));
|
CHECK(j == my_json({1, 2}));
|
||||||
|
|||||||
@@ -492,3 +492,278 @@ TEST_CASE("binary_reserve_hint never over-reserves")
|
|||||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// numbers around every boundary of the integer encodings, and floats of each
|
||||||
|
// kind (exact in float, needing double, special values)
|
||||||
|
std::vector<json> boundary_numbers()
|
||||||
|
{
|
||||||
|
std::vector<json> numbers;
|
||||||
|
for (int k = 0; k < 64; ++k)
|
||||||
|
{
|
||||||
|
const std::uint64_t p = std::uint64_t{1} << k;
|
||||||
|
for (const std::uint64_t d :
|
||||||
|
{
|
||||||
|
p - 1, p, p + 1
|
||||||
|
})
|
||||||
|
{
|
||||||
|
numbers.emplace_back(d);
|
||||||
|
if (d <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||||
|
{
|
||||||
|
numbers.emplace_back(static_cast<std::int64_t>(d));
|
||||||
|
numbers.emplace_back(-static_cast<std::int64_t>(d));
|
||||||
|
numbers.emplace_back(-static_cast<std::int64_t>(d) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numbers.emplace_back((std::numeric_limits<std::int64_t>::min)());
|
||||||
|
numbers.emplace_back((std::numeric_limits<std::uint64_t>::max)());
|
||||||
|
for (const double f :
|
||||||
|
{
|
||||||
|
0.0, -0.0, 1.0, -1.0, 0.5, 0.1, 3.25, 1e300, -1e-300, 3.4028234663852886e38, 1e39,
|
||||||
|
std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||||
|
std::numeric_limits<double>::quiet_NaN()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
numbers.emplace_back(f);
|
||||||
|
}
|
||||||
|
return numbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the concatenation of the encodings of the elements of @a numbers[first, first + count)
|
||||||
|
template<typename Single>
|
||||||
|
std::vector<std::uint8_t> concat_singles(const std::vector<json>& numbers, const std::size_t first, const std::size_t count, Single single)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> bytes;
|
||||||
|
for (std::size_t i = first; i < first + count; ++i)
|
||||||
|
{
|
||||||
|
const auto one = single(numbers[i]);
|
||||||
|
bytes.insert(bytes.end(), one.begin(), one.end());
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Runs of numbers in arrays are encoded straight into space reserved from the
|
||||||
|
// sink, by code that is separate from the encoding of a single value. Both have
|
||||||
|
// to agree on every number: an array is its head and the encodings of its
|
||||||
|
// elements, one after the other.
|
||||||
|
TEST_CASE("runs of numbers in arrays are encoded as the numbers one by one")
|
||||||
|
{
|
||||||
|
const std::vector<json> numbers = boundary_numbers();
|
||||||
|
|
||||||
|
// arrays of every length that a one-byte head covers, so that runs are as
|
||||||
|
// long as, and longer and shorter than, what fits one reservation
|
||||||
|
for (const std::size_t count :
|
||||||
|
{
|
||||||
|
1u, 2u, 3u, 5u, 14u, 15u, 23u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (std::size_t first = 0; first + count <= numbers.size(); first += 7)
|
||||||
|
{
|
||||||
|
const json array(std::vector<json>(numbers.begin() + static_cast<std::ptrdiff_t>(first),
|
||||||
|
numbers.begin() + static_cast<std::ptrdiff_t>(first + count)));
|
||||||
|
CAPTURE(array.dump(-1, ' ', false, json::error_handler_t::replace))
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> expected{static_cast<std::uint8_t>(0x80 + count)};
|
||||||
|
auto elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_cbor(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
CHECK(json::to_cbor(array) == expected);
|
||||||
|
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>(count <= 15 ? 0x90 + count : 0xDC));
|
||||||
|
if (count > 15)
|
||||||
|
{
|
||||||
|
expected.push_back(0);
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(count));
|
||||||
|
}
|
||||||
|
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_msgpack(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
CHECK(json::to_msgpack(array) == expected);
|
||||||
|
|
||||||
|
for (const bool bjdata :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>('['));
|
||||||
|
elements = concat_singles(numbers, first, count, [bjdata](const json & j)
|
||||||
|
{
|
||||||
|
return bjdata ? json::to_bjdata(j) : json::to_ubjson(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
expected.push_back(static_cast<std::uint8_t>(']'));
|
||||||
|
CHECK((bjdata ? json::to_bjdata(array) : json::to_ubjson(array)) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// BON8 has no integers above the int64 range
|
||||||
|
bool representable = true;
|
||||||
|
for (std::size_t i = first; i < first + count; ++i)
|
||||||
|
{
|
||||||
|
representable = representable && bon8_representable(numbers[i]);
|
||||||
|
}
|
||||||
|
if (representable)
|
||||||
|
{
|
||||||
|
expected.assign(1, static_cast<std::uint8_t>(count <= 4 ? 0x80 + count : 0x85));
|
||||||
|
elements = concat_singles(numbers, first, count, [](const json & j)
|
||||||
|
{
|
||||||
|
return json::to_bon8(j);
|
||||||
|
});
|
||||||
|
expected.insert(expected.end(), elements.begin(), elements.end());
|
||||||
|
if (count > 4)
|
||||||
|
{
|
||||||
|
expected.push_back(0xFE);
|
||||||
|
}
|
||||||
|
CHECK(json::to_bon8(array) == expected);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_THROWS_AS(json::to_bon8(array), json::out_of_range&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An array that is written in more than one reservation (more numbers than fit
|
||||||
|
// one), and arrays of an index with more digits, round trip for every format.
|
||||||
|
TEST_CASE("large arrays and many digits of indices round trip")
|
||||||
|
{
|
||||||
|
json array = json::array();
|
||||||
|
for (int i = 0; i < 12000; ++i)
|
||||||
|
{
|
||||||
|
switch (i % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
array.push_back(i);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
array.push_back(-i);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
array.push_back(i * 0.25);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
array.push_back(i % 8 == 3);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json document = json::object();
|
||||||
|
document["numbers"] = array;
|
||||||
|
document["nested"] = json::array({array, "text", array});
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||||
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings and keys are written with their head in one reservation up to a
|
||||||
|
// length; around that length, and with every kind of head, they must read back.
|
||||||
|
TEST_CASE("strings of every head length round trip")
|
||||||
|
{
|
||||||
|
json document = json::object();
|
||||||
|
json array = json::array();
|
||||||
|
for (const std::size_t length :
|
||||||
|
{
|
||||||
|
0u, 1u, 2u, 3u, 4u, 7u, 8u, 15u, 16u, 17u, 23u, 24u, 25u, 31u, 32u, 33u, 63u, 127u, 128u, 129u, 200u, 245u, 246u, 247u, 248u, 249u, 250u, 251u, 252u, 253u, 254u, 255u, 256u, 257u, 300u, 1000u, 65535u, 65536u, 70000u
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string text(length, 'x');
|
||||||
|
array.push_back(text);
|
||||||
|
document[std::string(length, 'k')] = text;
|
||||||
|
}
|
||||||
|
document["array"] = array;
|
||||||
|
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(document)) == document);
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document)) == document);
|
||||||
|
CHECK(json::from_ubjson(json::to_ubjson(document, true, true)) == document);
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(document)) == document);
|
||||||
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
CHECK(json::from_bon8(json::to_bon8(document)) == document);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// What a write that throws leaves behind in an output adapter is what was written
|
||||||
|
// before it, whatever the adapter and however the adapter sink buffers. (Without
|
||||||
|
// exceptions, the writes in CHECK_THROWS_AS do not run.)
|
||||||
|
TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||||
|
{
|
||||||
|
json array = json::array();
|
||||||
|
for (int i = 0; i < 700; ++i)
|
||||||
|
{
|
||||||
|
array.push_back(i * 1000);
|
||||||
|
array.push_back("text " + std::to_string(i));
|
||||||
|
}
|
||||||
|
array.push_back("bad \xFF byte");
|
||||||
|
json document = json::object();
|
||||||
|
document["first"] = array;
|
||||||
|
document["second"] = 1;
|
||||||
|
|
||||||
|
const auto strict = json::error_handler_t::strict;
|
||||||
|
|
||||||
|
SECTION("cbor")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_cbor(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("msgpack")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_msgpack(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ubjson")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_vector, false, false, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_string, false, false, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_ubjson(document, as_stream, false, false, strict), json::type_error&);
|
||||||
|
CHECK(!as_vector.empty());
|
||||||
|
CHECK(std::string(reinterpret_cast<const char*>(as_vector.data()), as_vector.size()) == as_string); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(as_stream.str() == as_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("bson")
|
||||||
|
{
|
||||||
|
// the size pass finds the string before anything is written
|
||||||
|
std::vector<std::uint8_t> as_vector;
|
||||||
|
std::string as_string;
|
||||||
|
std::ostringstream as_stream;
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_vector, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_string, strict), json::type_error&);
|
||||||
|
CHECK_THROWS_AS(json::to_bson(document, as_stream, strict), json::type_error&);
|
||||||
|
CHECK(as_vector.empty());
|
||||||
|
CHECK(as_string.empty());
|
||||||
|
CHECK(as_stream.str().empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-bon8.cpp (BON8 with std::byte
|
|
||||||
// containers). It is kept in a separate translation unit so the (much larger) unit-
|
|
||||||
// bon8.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
TEST_CASE("BON8 with std::byte")
|
|
||||||
{
|
|
||||||
SECTION("vector roundtrip")
|
|
||||||
{
|
|
||||||
const json original =
|
|
||||||
{
|
|
||||||
{"name", "test"},
|
|
||||||
{"value", 42},
|
|
||||||
{"array", {1, 2, 3}}
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<uint8_t> temp = json::to_bon8(original);
|
|
||||||
std::vector<std::byte> bon8_data(temp.size());
|
|
||||||
for (size_t i = 0; i < temp.size(); ++i)
|
|
||||||
{
|
|
||||||
bon8_data[i] = std::byte(temp[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
json from_bytes;
|
|
||||||
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
|
||||||
CHECK(from_bytes == original);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("empty vector")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> empty_data;
|
|
||||||
CHECK_THROWS_WITH_AS([&]()
|
|
||||||
{
|
|
||||||
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
(),
|
|
||||||
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
|
||||||
json::parse_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1099,3 +1099,41 @@ TEST_CASE("issue #5648 - from_bon8(ptr, len) must read len bytes, not treat ptr
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
TEST_CASE("BON8 with std::byte")
|
||||||
|
{
|
||||||
|
SECTION("vector roundtrip")
|
||||||
|
{
|
||||||
|
const json original =
|
||||||
|
{
|
||||||
|
{"name", "test"},
|
||||||
|
{"value", 42},
|
||||||
|
{"array", {1, 2, 3}}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<uint8_t> temp = json::to_bon8(original);
|
||||||
|
std::vector<std::byte> bon8_data(temp.size());
|
||||||
|
for (size_t i = 0; i < temp.size(); ++i)
|
||||||
|
{
|
||||||
|
bon8_data[i] = std::byte(temp[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json from_bytes;
|
||||||
|
CHECK_NOTHROW(from_bytes = json::from_bon8(bon8_data));
|
||||||
|
CHECK(from_bytes == original);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("empty vector")
|
||||||
|
{
|
||||||
|
const std::vector<std::byte> empty_data;
|
||||||
|
CHECK_THROWS_WITH_AS([&]()
|
||||||
|
{
|
||||||
|
[[maybe_unused]] auto result = json::from_bon8(empty_data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(),
|
||||||
|
"[json.exception.parse_error.110] parse error at byte 1: syntax error while parsing BON8 value: unexpected end of input",
|
||||||
|
json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -13,11 +13,15 @@
|
|||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
|
#include <cctype> // toupper
|
||||||
#include <cstdint> // uint32_t, uint64_t
|
#include <cstdint> // uint32_t, uint64_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cmath> // isfinite
|
||||||
#include <cstdlib> // strtod
|
#include <cstdlib> // strtod
|
||||||
#include <cstring> // memcpy
|
#include <cstring> // memcpy
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
|
#include <initializer_list> // initializer_list
|
||||||
|
#include <list> // list
|
||||||
#include <map> // map
|
#include <map> // map
|
||||||
#include <random> // mt19937
|
#include <random> // mt19937
|
||||||
#include <sstream> // stringstream
|
#include <sstream> // stringstream
|
||||||
@@ -332,6 +336,112 @@ TEST_CASE("lexer number fast path")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// this section reports parse errors, which abort when exceptions are off
|
||||||
|
SECTION("digits accumulated while scanning")
|
||||||
|
{
|
||||||
|
// The fast path accumulates the digits while it validates them, eight
|
||||||
|
// at a time where possible, and converts tokens with at most 19
|
||||||
|
// digits (floats) or 18 digits (integers) from that value. Every digit
|
||||||
|
// count from 1 to 25, in the integer and in the fraction part, with
|
||||||
|
// and without sign and exponent, must give the byte path's result. A
|
||||||
|
// std::list has no contiguous storage, so parsing it takes the byte
|
||||||
|
// path.
|
||||||
|
// type and value, or the error (values beyond the range of double
|
||||||
|
// are rejected with out_of_range.406 on both paths)
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json j = byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc);
|
||||||
|
return std::string(j[0].type_name()) + '|' + j.dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the concatenation of @a parts
|
||||||
|
const auto join = [](std::initializer_list<std::string> parts)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
for (const auto& part : parts)
|
||||||
|
{
|
||||||
|
result += part;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> numbers;
|
||||||
|
for (std::size_t digits = 1; digits <= 25; ++digits)
|
||||||
|
{
|
||||||
|
std::string run;
|
||||||
|
for (std::size_t i = 0; i < digits; ++i)
|
||||||
|
{
|
||||||
|
run += static_cast<char>('1' + static_cast<char>((i * 7) % 9));
|
||||||
|
}
|
||||||
|
const std::string nines(digits, '9');
|
||||||
|
for (const char* sign_text :
|
||||||
|
{"", "-"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
const std::string sign = sign_text;
|
||||||
|
numbers.push_back(join({sign, run}));
|
||||||
|
numbers.push_back(join({sign, nines}));
|
||||||
|
numbers.push_back(join({sign, "1", std::string(digits - 1, '0')}));
|
||||||
|
numbers.push_back(join({sign, "0.", run}));
|
||||||
|
numbers.push_back(join({sign, "0.", std::string(digits, '0'), "1"}));
|
||||||
|
numbers.push_back(join({sign, run, ".", run}));
|
||||||
|
numbers.push_back(join({sign, run, "e-", std::to_string(digits)}));
|
||||||
|
numbers.push_back(join({sign, "1.", run, "E+", std::to_string(digits * 13)}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// exponents beyond every range, and zeros
|
||||||
|
numbers.insert(numbers.end(), {"1e400", "1e-400", "-1e99999999999999999999", "1e-99999999999999999999",
|
||||||
|
"0e99999999999999999999", "0.0e-5", "-0.000", "123456789e-0000000000000000000000330"
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
const std::string a = outcome(doc, false);
|
||||||
|
CHECK(a == outcome(doc, true));
|
||||||
|
const double expected = std::strtod(n.c_str(), nullptr);
|
||||||
|
if (a.compare(0, 6, "number") == 0 && std::isfinite(expected) && n.find_first_of(".eE") != std::string::npos)
|
||||||
|
{
|
||||||
|
CHECK(json::parse(doc)[0].get<double>() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SECTION("integers that do not fit a narrower type become floats")
|
||||||
|
{
|
||||||
|
// up to 18 digits, the fast path stores the accumulated value without
|
||||||
|
// an overflow check; a narrower number_integer_t/number_unsigned_t
|
||||||
|
// must still see the value rejected and converted as a float
|
||||||
|
using json32 = nlohmann::basic_json<std::map, std::vector, std::string, bool, std::int32_t, std::uint32_t, float>;
|
||||||
|
for (const char* n :
|
||||||
|
{"4294967295", "4294967296", "2147483647", "-2147483648", "-2147483649", "99999999999", "-99999999999"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(n)
|
||||||
|
const std::string doc = std::string("[") + n + "]";
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
const json32 a = json32::parse(doc);
|
||||||
|
const json32 b = json32::parse(chars.begin(), chars.end());
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
CHECK(a == b);
|
||||||
|
}
|
||||||
|
CHECK(json32::parse("[4294967295]")[0].is_number_unsigned());
|
||||||
|
CHECK(json32::parse("[4294967296]")[0].is_number_float());
|
||||||
|
CHECK(json32::parse("[-2147483648]")[0].is_number_integer());
|
||||||
|
CHECK(json32::parse("[-2147483649]")[0].is_number_float());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("token type classification")
|
SECTION("token type classification")
|
||||||
{
|
{
|
||||||
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
@@ -490,6 +600,67 @@ TEST_CASE("lexer number fast path")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// this test case compares the errors of both lexer paths, which abort when
|
||||||
|
// exceptions are off
|
||||||
|
TEST_CASE("lexer literal fast path")
|
||||||
|
{
|
||||||
|
// For contiguous input, the rest of a literal is compared at once, and a
|
||||||
|
// mismatch is left to the byte path, which reports the error at the
|
||||||
|
// character that differs. Every truncation and every wrong character of
|
||||||
|
// each literal, at the end of the input or followed by more text, must
|
||||||
|
// give the byte path's result (a std::list is read one character at a
|
||||||
|
// time).
|
||||||
|
const auto outcome = [](const std::string & doc, bool byte_path) -> std::string
|
||||||
|
{
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
return (byte_path ? json::parse(chars.begin(), chars.end()) : json::parse(doc)).dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (const std::string literal :
|
||||||
|
{"true", "false", "null"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::vector<std::string> variants;
|
||||||
|
for (std::size_t length = 1; length <= literal.size(); ++length)
|
||||||
|
{
|
||||||
|
variants.push_back(literal.substr(0, length));
|
||||||
|
}
|
||||||
|
for (std::size_t i = 1; i < literal.size(); ++i)
|
||||||
|
{
|
||||||
|
std::string changed = literal;
|
||||||
|
changed[i] = 'X';
|
||||||
|
variants.push_back(changed);
|
||||||
|
changed[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(literal[i])));
|
||||||
|
variants.push_back(changed);
|
||||||
|
}
|
||||||
|
for (const auto& variant : variants)
|
||||||
|
{
|
||||||
|
for (const std::string& doc :
|
||||||
|
{
|
||||||
|
variant, "[" + variant + "]", "[" + variant, variant + " ", variant + "1", "{\"k\":" + variant + "}"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches)
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_CASE("lexer string fast path")
|
TEST_CASE("lexer string fast path")
|
||||||
{
|
{
|
||||||
// Build a byte string from explicit values: a hex escape in a string
|
// Build a byte string from explicit values: a hex escape in a string
|
||||||
@@ -610,6 +781,73 @@ TEST_CASE("lexer string fast path")
|
|||||||
CAPTURE(mismatches)
|
CAPTURE(mismatches)
|
||||||
CHECK(mismatches.empty());
|
CHECK(mismatches.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("special bytes at every offset of the vector blocks")
|
||||||
|
{
|
||||||
|
// With SSE2 or NEON, runs are scanned 16 bytes at a time (after one
|
||||||
|
// SWAR word, or four with NEON), and runs of non-ASCII text are
|
||||||
|
// checked 16 bytes at a time as well. Place every kind of byte that
|
||||||
|
// ends a run at each offset across several blocks, after ASCII and
|
||||||
|
// after CJK text, so sequences also straddle the block boundaries.
|
||||||
|
// The reference is a std::list, which has no contiguous storage and
|
||||||
|
// is therefore read one character at a time.
|
||||||
|
const auto byte_path = [](const std::string & doc) -> std::string
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
JSON_TRY
|
||||||
|
{
|
||||||
|
return json::parse(chars.begin(), chars.end()).dump();
|
||||||
|
}
|
||||||
|
JSON_CATCH (const json::exception & e)
|
||||||
|
{
|
||||||
|
return e.what();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const std::vector<std::string> specials =
|
||||||
|
{
|
||||||
|
"\"", "\\", "\\u00e9", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
|
||||||
|
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
|
||||||
|
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8}), bytes({0xED, 0xA0, 0x80}), bytes({0xC0, 0x80})
|
||||||
|
};
|
||||||
|
const std::string cjk = bytes({0xE4, 0xB8, 0xAD}); // U+4E2D
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (std::size_t offset = 0; offset <= 80; ++offset)
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
while (text.size() < offset)
|
||||||
|
{
|
||||||
|
text += cjk;
|
||||||
|
}
|
||||||
|
for (const auto& prefix :
|
||||||
|
{
|
||||||
|
std::string(offset, 'a'), text
|
||||||
|
})
|
||||||
|
{
|
||||||
|
for (const auto& special : specials)
|
||||||
|
{
|
||||||
|
// the closing quote right behind, or in a later block
|
||||||
|
for (const int tail :
|
||||||
|
{
|
||||||
|
0, 1, 3, 20
|
||||||
|
})
|
||||||
|
{
|
||||||
|
std::string doc = "[\"";
|
||||||
|
doc += prefix;
|
||||||
|
doc += special;
|
||||||
|
doc.append(static_cast<std::size_t>(tail), 'b');
|
||||||
|
doc += "\"]";
|
||||||
|
if (outcome(doc, false) != byte_path(doc))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches)
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// json::accept() never throws, so the ranges stay covered without exceptions
|
// json::accept() never throws, so the ranges stay covered without exceptions
|
||||||
@@ -1804,3 +2042,118 @@ TEST_CASE("string scanning kernels")
|
|||||||
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
CHECK(nlohmann::detail::read_eight_bytes(bytes.data() + 8) == 0xFF0F0E0D0C0B0A09u);
|
||||||
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
CHECK(nlohmann::detail::read_eight_bytes(reinterpret_cast<const char*>(bytes.data()) + 3) == 0x0B0A090807060504u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("find_ascii_copyable_run agrees with is_ascii_copyable for every byte")
|
||||||
|
{
|
||||||
|
// the SWAR mask in find_ascii_copyable_run() encodes the predicate a
|
||||||
|
// second time; check every byte value at every position of a 24-byte buffer
|
||||||
|
// (three words) and for several lengths, with the byte at the end or inside
|
||||||
|
for (unsigned int value = 0; value < 256; ++value)
|
||||||
|
{
|
||||||
|
for (std::size_t position = 0; position < 24; ++position)
|
||||||
|
{
|
||||||
|
std::array<unsigned char, 24> buffer{};
|
||||||
|
buffer.fill(static_cast<unsigned char>('a'));
|
||||||
|
buffer[position] = static_cast<unsigned char>(value);
|
||||||
|
const bool copyable = nlohmann::detail::is_ascii_copyable(static_cast<unsigned char>(value));
|
||||||
|
for (const std::size_t n :
|
||||||
|
{
|
||||||
|
position, position + 1, std::size_t{24}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (n > 24)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::size_t expected = (copyable || n <= position) ? n : position;
|
||||||
|
CAPTURE(value)
|
||||||
|
CAPTURE(position)
|
||||||
|
CAPTURE(n)
|
||||||
|
CHECK(nlohmann::detail::find_ascii_copyable_run(buffer.data(), n) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UTF-8 validators accept the same sequences")
|
||||||
|
{
|
||||||
|
// The library checks UTF-8 in several places: detail::decode (serializer),
|
||||||
|
// the per-lead-byte switch of the lexer (json::accept), validate_one_utf8
|
||||||
|
// (bulk string scan), utf8_shape, and the byte path of the BON8 reader.
|
||||||
|
// They must accept exactly the same sequences (RFC 3629, section 4).
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
// bytes for the third and fourth position: the first two pairs are checked
|
||||||
|
// against all validators, the others only against the cheap ones
|
||||||
|
const std::array<std::array<unsigned char, 2>, 6> tails =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{{0x80, 0x80}}, {{0xBF, 0xBF}}, {{0x7F, 0x80}}, {{0xC0, 0x80}}, {{0x80, 0x7F}}, {{0x80, 0xC0}}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (unsigned lead = 0x80; lead <= 0xFF; ++lead)
|
||||||
|
{
|
||||||
|
const auto shape = nlohmann::detail::utf8_shape(static_cast<std::uint8_t>(lead));
|
||||||
|
for (unsigned second = 0; second <= 0xFF; ++second)
|
||||||
|
{
|
||||||
|
for (std::size_t t = 0; t < tails.size(); ++t)
|
||||||
|
{
|
||||||
|
const std::array<unsigned char, 4> bytes = {{static_cast<unsigned char>(lead), static_cast<unsigned char>(second), tails[t][0], tails[t][1]}};
|
||||||
|
|
||||||
|
// expectation from the shape: the sequence is bytes[0..length)
|
||||||
|
const std::size_t length = shape.length;
|
||||||
|
bool expected = length >= 2 && second >= shape.second_lo && second <= shape.second_hi;
|
||||||
|
for (std::size_t i = 2; expected && i < length; ++i)
|
||||||
|
{
|
||||||
|
expected = bytes[i] >= 0x80 && bytes[i] <= 0xBF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the DFA of the serializer
|
||||||
|
bool dfa = length >= 2;
|
||||||
|
if (dfa)
|
||||||
|
{
|
||||||
|
std::uint8_t state = nlohmann::detail::UTF8_ACCEPT;
|
||||||
|
std::uint32_t codep = 0;
|
||||||
|
for (std::size_t i = 0; i < length; ++i)
|
||||||
|
{
|
||||||
|
nlohmann::detail::decode(state, codep, bytes[i]);
|
||||||
|
}
|
||||||
|
dfa = state == nlohmann::detail::UTF8_ACCEPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the bulk path: exactly the sequence, and one byte short
|
||||||
|
const std::size_t bulk = nlohmann::detail::validate_one_utf8(bytes.data(), length);
|
||||||
|
const std::size_t bulk_short = length > 0 ? nlohmann::detail::validate_one_utf8(bytes.data(), length - 1) : 0;
|
||||||
|
|
||||||
|
CAPTURE(lead)
|
||||||
|
CAPTURE(second)
|
||||||
|
CAPTURE(t)
|
||||||
|
CHECK((dfa == expected && bulk == (expected ? length : 0) && bulk_short == 0));
|
||||||
|
|
||||||
|
if (t < 2 && length >= 2)
|
||||||
|
{
|
||||||
|
// the lexer's byte path
|
||||||
|
const std::string sequence(reinterpret_cast<const char*>(bytes.data()), length); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||||
|
CHECK(json::accept("\"" + sequence + "\"") == expected);
|
||||||
|
|
||||||
|
// the byte path of the BON8 reader (a string ends at 0xFF)
|
||||||
|
if (lead >= 0xC2 && lead <= 0xF7)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> packed(bytes.begin(), bytes.begin() + static_cast<std::ptrdiff_t>(length));
|
||||||
|
packed.push_back(0xFF);
|
||||||
|
const json j = json::from_bon8(packed, true, false);
|
||||||
|
if (expected)
|
||||||
|
{
|
||||||
|
CHECK(j == json(sequence));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CHECK_FALSE((j.is_string() && j.get<std::string>() == sequence));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,9 @@ using nlohmann::json;
|
|||||||
#include <valarray>
|
#include <valarray>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -2450,6 +2452,109 @@ std::string parse_error_message_range(IteratorType first, IteratorType last)
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("object keys are moved into the value")
|
||||||
|
{
|
||||||
|
// json_sax_dom_parser::key() moves the key out of the lexer's buffer into
|
||||||
|
// the object, and the lexer starts the next token with an empty buffer.
|
||||||
|
// Keys of every length (inline and allocated strings), keys with escapes,
|
||||||
|
// and duplicate keys must give the same objects as reading the input one
|
||||||
|
// character at a time, and as ordered_json, whose map copies the key.
|
||||||
|
const std::string long_key(100, 'k');
|
||||||
|
const std::string doc = R"({"a": 1, ")" + long_key + R"(": {"b": [2, 3], "sixteen_chars_xx": 4}, )"
|
||||||
|
R"("esc\"apedé": 5, "a": 6, ")" + long_key + R"(x": 7, "": 8, "dup": 9, "dup": 10})";
|
||||||
|
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
CHECK(j == json::parse(chars.begin(), chars.end()));
|
||||||
|
|
||||||
|
CHECK(j.size() == 6);
|
||||||
|
CHECK(j["a"] == 6);
|
||||||
|
CHECK(j[long_key]["b"] == json({2, 3}));
|
||||||
|
CHECK(j[long_key]["sixteen_chars_xx"] == 4);
|
||||||
|
CHECK(j["esc\"aped\xc3\xa9"] == 5);
|
||||||
|
CHECK(j[long_key + "x"] == 7);
|
||||||
|
CHECK(j[""] == 8);
|
||||||
|
CHECK(j["dup"] == 10);
|
||||||
|
|
||||||
|
const auto o = nlohmann::ordered_json::parse(doc);
|
||||||
|
CHECK(o.size() == 6);
|
||||||
|
CHECK(o[long_key + "x"] == 7);
|
||||||
|
CHECK(json(o) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("text of float tokens")
|
||||||
|
{
|
||||||
|
// For contiguous input, the lexer copies the text of a float token into
|
||||||
|
// its buffer only when a SAX handler asks for it (the library's own
|
||||||
|
// handlers do not). A user handler must still see the exact text, for
|
||||||
|
// every input adapter and in every position of a document.
|
||||||
|
const std::string doc = R"([1.5, -0.0, 0.1, 1e5, -2.5E-4, 12345678901234567890.5, 0.12345678901234567890123,
|
||||||
|
{"a": 3.25, "b": [6.02e23, 1]}, "x", true, 7e-1])";
|
||||||
|
const std::vector<std::string> expected_floats =
|
||||||
|
{
|
||||||
|
"number_float(1.5)", "number_float(-0.0)", "number_float(0.1)", "number_float(1e5)", "number_float(-2.5E-4)",
|
||||||
|
"number_float(12345678901234567890.5)", "number_float(0.12345678901234567890123)", "number_float(3.25)",
|
||||||
|
"number_float(6.02e23)", "number_float(7e-1)"
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto floats = [](const std::vector<std::string>& events)
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
std::copy_if(events.begin(), events.end(), std::back_inserter(result), [](const std::string & e)
|
||||||
|
{
|
||||||
|
return e.compare(0, 13, "number_float(") == 0;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("contiguous input")
|
||||||
|
{
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(doc, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("byte input")
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(bytes, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("stream input")
|
||||||
|
{
|
||||||
|
std::istringstream is(doc);
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(is, &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("non-contiguous iterators")
|
||||||
|
{
|
||||||
|
const std::list<char> chars(doc.begin(), doc.end());
|
||||||
|
SaxEventLogger l;
|
||||||
|
CHECK(json::sax_parse(chars.begin(), chars.end(), &l));
|
||||||
|
CHECK(floats(l.events) == expected_floats);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the library's own handlers do not need the text")
|
||||||
|
{
|
||||||
|
using lexer_adapter = nlohmann::detail::iterator_input_adapter<const char*>;
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_dom_callback_parser<json, lexer_adapter>>::value);
|
||||||
|
CHECK(nlohmann::detail::sax_ignores_number_text<nlohmann::detail::json_sax_acceptor<json>>::value);
|
||||||
|
CHECK_FALSE(nlohmann::detail::sax_ignores_number_text<SaxEventLogger>::value);
|
||||||
|
|
||||||
|
// and the values are the same as when the text is read
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
CHECK(j[0] == 1.5);
|
||||||
|
CHECK(j[5].get<double>() == 12345678901234567890.5);
|
||||||
|
CHECK(j[7]["b"][0] == 6.02e23);
|
||||||
|
CHECK(json::accept(doc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("last-read diagnostics are identical across input adapters")
|
TEST_CASE("last-read diagnostics are identical across input adapters")
|
||||||
{
|
{
|
||||||
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
// The lexer reconstructs the "last read" token lazily for seekable adapters
|
||||||
|
|||||||
@@ -1,526 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++20-only part of unit-comparison.cpp (tests of operator<=> and
|
|
||||||
// other three-way comparison specific behavior). It is kept in a separate translation unit
|
|
||||||
// so the (much larger) unit-comparison.cpp is built for C++11 only and not rebuilt for
|
|
||||||
// every C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_20
|
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
#include <cmath>
|
|
||||||
#include <compare>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <limits>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// this can be replaced with the doctest stl extension header in version 2.5
|
|
||||||
namespace doctest
|
|
||||||
{
|
|
||||||
template<> struct StringMaker<std::partial_ordering>
|
|
||||||
{
|
|
||||||
static String convert(const std::partial_ordering& order)
|
|
||||||
{
|
|
||||||
if (order == std::partial_ordering::less)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::less";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::equivalent)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::equivalent";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::greater)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::greater";
|
|
||||||
}
|
|
||||||
if (order == std::partial_ordering::unordered)
|
|
||||||
{
|
|
||||||
return "std::partial_ordering::unordered";
|
|
||||||
}
|
|
||||||
return "{?}";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace doctest
|
|
||||||
|
|
||||||
TEST_CASE("lexicographical comparison operators (C++20)")
|
|
||||||
{
|
|
||||||
constexpr auto f_ = false;
|
|
||||||
constexpr auto _t = true;
|
|
||||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
|
||||||
constexpr auto lt = std::partial_ordering::less;
|
|
||||||
constexpr auto gt = std::partial_ordering::greater;
|
|
||||||
constexpr auto eq = std::partial_ordering::equivalent;
|
|
||||||
constexpr auto un = std::partial_ordering::unordered;
|
|
||||||
|
|
||||||
INFO("using 3-way comparison");
|
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
||||||
INFO("using legacy comparison");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SECTION("types")
|
|
||||||
{
|
|
||||||
std::vector<json::value_t> j_types =
|
|
||||||
{
|
|
||||||
json::value_t::null,
|
|
||||||
json::value_t::boolean,
|
|
||||||
json::value_t::number_integer,
|
|
||||||
json::value_t::number_unsigned,
|
|
||||||
json::value_t::number_float,
|
|
||||||
json::value_t::object,
|
|
||||||
json::value_t::array,
|
|
||||||
json::value_t::string,
|
|
||||||
json::value_t::binary,
|
|
||||||
json::value_t::discarded
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::vector<bool>> expected_lt =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9
|
|
||||||
{f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0
|
|
||||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1
|
|
||||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2
|
|
||||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3
|
|
||||||
{f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4
|
|
||||||
{f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("comparison: less")
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < j_types.size(); ++i)
|
|
||||||
{
|
|
||||||
for (size_t j = 0; j < j_types.size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
// check precomputed values
|
|
||||||
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("comparison: 3-way")
|
|
||||||
{
|
|
||||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
|
||||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
|
||||||
REQUIRE(std::isnan(nan));
|
|
||||||
|
|
||||||
std::vector<std::vector<std::partial_ordering>> expected =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9
|
|
||||||
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
|
||||||
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
|
||||||
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
|
||||||
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
|
||||||
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
|
||||||
{un, un, un, un, un, un, un, un, un, un}, // 9
|
|
||||||
};
|
|
||||||
|
|
||||||
// check expected partial_ordering against expected boolean
|
|
||||||
REQUIRE(expected.size() == expected_lt.size());
|
|
||||||
for (size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
||||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check 3-way comparison against expected partial_ordering
|
|
||||||
REQUIRE(expected.size() == j_types.size());
|
|
||||||
for (size_t i = 0; i < j_types.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == j_types.size());
|
|
||||||
for (size_t j = 0; j < j_types.size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("values")
|
|
||||||
{
|
|
||||||
json j_values =
|
|
||||||
{
|
|
||||||
nullptr, nullptr, // 0 1
|
|
||||||
-17, 42, // 2 3
|
|
||||||
8u, 13u, // 4 5
|
|
||||||
3.14159, 23.42, // 6 7
|
|
||||||
nan, nan, // 8 9
|
|
||||||
"foo", "bar", // 10 11
|
|
||||||
true, false, // 12 13
|
|
||||||
{1, 2, 3}, {"one", "two", "three"}, // 14 15
|
|
||||||
{{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17
|
|
||||||
json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19
|
|
||||||
json(json::value_t::discarded), json(json::value_t::discarded) // 20 21
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::vector<bool>> expected_eq =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
||||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0
|
|
||||||
{_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1
|
|
||||||
{f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2
|
|
||||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3
|
|
||||||
{f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4
|
|
||||||
{f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5
|
|
||||||
{f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::vector<bool>> expected_lt =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
||||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0
|
|
||||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1
|
|
||||||
{f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3
|
|
||||||
{f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4
|
|
||||||
{f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5
|
|
||||||
{f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6
|
|
||||||
{f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11
|
|
||||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12
|
|
||||||
{f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20
|
|
||||||
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
|
|
||||||
};
|
|
||||||
|
|
||||||
SECTION("signed/unsigned mixed comparison above INT64_MAX")
|
|
||||||
{
|
|
||||||
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
|
|
||||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
|
||||||
const json negative_one = -1;
|
|
||||||
const json one = 1;
|
|
||||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
|
||||||
|
|
||||||
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("integer/float mixed comparison is exact")
|
|
||||||
{
|
|
||||||
// Widening the integer to a double loses precision past the
|
|
||||||
// mantissa, so 2^63-2 and 2^63-1 both used to compare equal to the
|
|
||||||
// double 2^63 while differing from each other. That makes equality
|
|
||||||
// intransitive and the ordering not a strict weak ordering.
|
|
||||||
const json below_two_63 = static_cast<std::int64_t>(9223372036854775806LL);
|
|
||||||
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
|
|
||||||
const json two_63 = 9223372036854775808.0;
|
|
||||||
|
|
||||||
// the same past the unsigned range
|
|
||||||
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
|
|
||||||
const json two_64 = 18446744073709551616.0;
|
|
||||||
|
|
||||||
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("comparison: 3-way")
|
|
||||||
{
|
|
||||||
// doctest runs the REQUIRE in the test case body once per leaf section; keep it
|
|
||||||
// here to run it as often as before the 3-way sections moved from unit-comparison.cpp
|
|
||||||
REQUIRE(std::isnan(nan));
|
|
||||||
|
|
||||||
std::vector<std::vector<std::partial_ordering>> expected =
|
|
||||||
{
|
|
||||||
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
|
||||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
|
||||||
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
|
||||||
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
|
||||||
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
|
||||||
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
|
||||||
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
|
||||||
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
|
||||||
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
|
||||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
|
||||||
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
|
||||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
|
||||||
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
|
||||||
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
|
||||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
|
||||||
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
|
||||||
};
|
|
||||||
|
|
||||||
// check expected partial_ordering against expected booleans
|
|
||||||
REQUIRE(expected.size() == expected_eq.size());
|
|
||||||
REQUIRE(expected.size() == expected_lt.size());
|
|
||||||
for (size_t i = 0; i < expected.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == expected_eq[i].size());
|
|
||||||
REQUIRE(expected[i].size() == expected_lt[i].size());
|
|
||||||
for (size_t j = 0; j < expected[i].size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
|
||||||
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
|
||||||
if (std::is_gt(expected[i][j]))
|
|
||||||
{
|
|
||||||
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that two values compare according to their expected ordering
|
|
||||||
REQUIRE(expected.size() == j_values.size());
|
|
||||||
for (size_t i = 0; i < j_values.size(); ++i)
|
|
||||||
{
|
|
||||||
REQUIRE(expected[i].size() == j_values.size());
|
|
||||||
for (size_t j = 0; j < j_values.size(); ++j)
|
|
||||||
{
|
|
||||||
CAPTURE(i)
|
|
||||||
CAPTURE(j)
|
|
||||||
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
|
||||||
{
|
|
||||||
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
|
||||||
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
|
||||||
// Verify that heterogeneous comparisons now work.
|
|
||||||
|
|
||||||
SECTION("string vs json")
|
|
||||||
{
|
|
||||||
std::string s = "string";
|
|
||||||
json j = "string";
|
|
||||||
CHECK(s == j);
|
|
||||||
CHECK(j == s);
|
|
||||||
CHECK_FALSE(s != j);
|
|
||||||
CHECK_FALSE(j != s);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("other heterogeneous types")
|
|
||||||
{
|
|
||||||
int i = 42;
|
|
||||||
json j = 42;
|
|
||||||
CHECK(i == j);
|
|
||||||
CHECK(j == i);
|
|
||||||
CHECK_FALSE(i != j);
|
|
||||||
CHECK_FALSE(j != i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
|
||||||
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
|
||||||
{
|
|
||||||
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
|
||||||
// candidate rewritten from operator<=>, which does not emulate the legacy
|
|
||||||
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
|
||||||
// other three operand orders.
|
|
||||||
const json discarded(json::value_t::discarded);
|
|
||||||
const json one = 1;
|
|
||||||
|
|
||||||
CHECK(discarded <= 1);
|
|
||||||
CHECK(discarded >= 1);
|
|
||||||
CHECK(one <= discarded);
|
|
||||||
CHECK(one >= discarded);
|
|
||||||
CHECK(1 <= discarded);
|
|
||||||
CHECK(1 >= discarded);
|
|
||||||
CHECK(1.5 <= discarded);
|
|
||||||
CHECK(1.5 >= discarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("containers are compared element by element (C++20)")
|
|
||||||
{
|
|
||||||
// Containers nested deeper than a bound are compared without the call
|
|
||||||
// stack, by code of their own; every relation is checked both at the top
|
|
||||||
// level and below that bound.
|
|
||||||
const auto deep = [](const json & j, const std::size_t depth)
|
|
||||||
{
|
|
||||||
json result = j;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
result = json::array({std::move(result)});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 200})
|
|
||||||
{
|
|
||||||
CAPTURE(depth)
|
|
||||||
|
|
||||||
// objects with different keys
|
|
||||||
{
|
|
||||||
const json a = deep({{"a", 1}}, depth);
|
|
||||||
const json b = deep({{"b", 1}}, depth);
|
|
||||||
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
}
|
|
||||||
|
|
||||||
// a container that is a prefix of the other one
|
|
||||||
{
|
|
||||||
// the one that runs out of elements first is the smaller one
|
|
||||||
const json shorter = deep({1}, depth);
|
|
||||||
const json longer = deep({1, 2}, depth);
|
|
||||||
|
|
||||||
const json smaller_object = deep({{"a", 1}}, depth);
|
|
||||||
const json larger_object = deep({{"a", 1}, {"b", 2}}, depth);
|
|
||||||
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
}
|
|
||||||
|
|
||||||
// elements that cannot be ordered
|
|
||||||
{
|
|
||||||
const double nan = std::numeric_limits<double>::quiet_NaN();
|
|
||||||
const json lhs = deep({nan, 1}, depth);
|
|
||||||
const json rhs = deep({nan, 2}, depth);
|
|
||||||
// operator<=> stops there, as std::lexicographical_compare_three_way
|
|
||||||
// does, and operator< is derived from it
|
|
||||||
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK_FALSE(lhs < rhs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
|
||||||
{
|
|
||||||
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
|
||||||
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
|
||||||
// pair of binary values with the same bytes but a different subtype is
|
|
||||||
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
|
||||||
// that a NaN has. Within the nesting bound, an array compares itself
|
|
||||||
// with std::vector's own operator<=>, which treats an equivalent pair as
|
|
||||||
// undecided and lets the next element decide, same as
|
|
||||||
// std::lexicographical_compare_three_way does. Past the bound,
|
|
||||||
// compare_iteratively<true>() takes over and must classify the pair the
|
|
||||||
// same way, or the result of operator<=> - and of <, which C++20 derives
|
|
||||||
// from it - depends on how deeply the values are nested.
|
|
||||||
const json a = json::array({json::binary({1}, 1), 1});
|
|
||||||
const json b = json::array({json::binary({1}, 2), 2});
|
|
||||||
|
|
||||||
// the root inconsistency: unequal, yet <=>-equivalent
|
|
||||||
CHECK_FALSE(a[0] == b[0]);
|
|
||||||
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
|
|
||||||
const auto deep = [](const json & j, const std::size_t depth)
|
|
||||||
{
|
|
||||||
json result = j;
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
result = json::array({std::move(result)});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
|
||||||
// and must still agree with the levels that do
|
|
||||||
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
|
||||||
{
|
|
||||||
CAPTURE(depth)
|
|
||||||
const json x = deep(a, depth);
|
|
||||||
const json y = deep(b, depth);
|
|
||||||
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK(x < y);
|
|
||||||
CHECK(y > x);
|
|
||||||
CHECK_FALSE(y < x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("comparison of scalars (inline fast path): operator<=>")
|
|
||||||
{
|
|
||||||
// the three-way part of "comparison of scalars (inline fast path)" in
|
|
||||||
// unit-comparison.cpp, which is built for C++11 only
|
|
||||||
const json::number_integer_t neg = -1;
|
|
||||||
|
|
||||||
SECTION("NaN")
|
|
||||||
{
|
|
||||||
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
|
||||||
const json one = 1.0;
|
|
||||||
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
|
||||||
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
|
||||||
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("discarded")
|
|
||||||
{
|
|
||||||
const json d(json::value_t::discarded);
|
|
||||||
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // JSON_HAS_THREE_WAY_COMPARISON
|
|
||||||
#endif
|
|
||||||
+356
-59
@@ -6,11 +6,12 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
// When using macros that are only defined for particular versions of the standard
|
||||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||||
// go into a separate file unit-comparison-cpp<NN>.cpp. This file stays C++11-only.
|
// version macro in a comment close by, like this:
|
||||||
|
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -28,6 +29,37 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// this can be replaced with the doctest stl extension header in version 2.5
|
||||||
|
namespace doctest
|
||||||
|
{
|
||||||
|
template<> struct StringMaker<std::partial_ordering>
|
||||||
|
{
|
||||||
|
static String convert(const std::partial_ordering& order)
|
||||||
|
{
|
||||||
|
if (order == std::partial_ordering::less)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::less";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::equivalent)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::equivalent";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::greater)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::greater";
|
||||||
|
}
|
||||||
|
if (order == std::partial_ordering::unordered)
|
||||||
|
{
|
||||||
|
return "std::partial_ordering::unordered";
|
||||||
|
}
|
||||||
|
return "{?}";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace doctest
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// helper function to check std::less<json::value_t>
|
// helper function to check std::less<json::value_t>
|
||||||
@@ -44,6 +76,16 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
constexpr auto f_ = false;
|
constexpr auto f_ = false;
|
||||||
constexpr auto _t = true;
|
constexpr auto _t = true;
|
||||||
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
constexpr auto nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
constexpr auto lt = std::partial_ordering::less;
|
||||||
|
constexpr auto gt = std::partial_ordering::greater;
|
||||||
|
constexpr auto eq = std::partial_ordering::equivalent;
|
||||||
|
constexpr auto un = std::partial_ordering::unordered;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
INFO("using 3-way comparison");
|
||||||
|
#endif
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
INFO("using legacy comparison");
|
INFO("using legacy comparison");
|
||||||
@@ -94,13 +136,62 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CAPTURE(i)
|
CAPTURE(i)
|
||||||
CAPTURE(j)
|
CAPTURE(j)
|
||||||
// check precomputed values
|
// check precomputed values
|
||||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]);
|
||||||
|
#else
|
||||||
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||||
#endif
|
#endif
|
||||||
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
SECTION("comparison: 3-way")
|
||||||
|
{
|
||||||
|
std::vector<std::vector<std::partial_ordering>> expected =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9
|
||||||
|
{eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0
|
||||||
|
{gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3
|
||||||
|
{gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4
|
||||||
|
{gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5
|
||||||
|
{gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8
|
||||||
|
{un, un, un, un, un, un, un, un, un, un}, // 9
|
||||||
|
};
|
||||||
|
|
||||||
|
// check expected partial_ordering against expected boolean
|
||||||
|
REQUIRE(expected.size() == expected_lt.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||||
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check 3-way comparison against expected partial_ordering
|
||||||
|
REQUIRE(expected.size() == j_types.size());
|
||||||
|
for (size_t i = 0; i < j_types.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == j_types.size());
|
||||||
|
for (size_t j = 0; j < j_types.size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("values")
|
SECTION("values")
|
||||||
@@ -229,6 +320,18 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CHECK_FALSE(above_int64_max <= max_int64);
|
CHECK_FALSE(above_int64_max <= max_int64);
|
||||||
CHECK(above_int64_max > max_int64);
|
CHECK(above_int64_max > max_int64);
|
||||||
CHECK(above_int64_max >= max_int64);
|
CHECK(above_int64_max >= max_int64);
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("integer/float mixed comparison is exact")
|
SECTION("integer/float mixed comparison is exact")
|
||||||
@@ -279,6 +382,16 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
CHECK_FALSE(json(1) < json(nan));
|
CHECK_FALSE(json(1) < json(nan));
|
||||||
CHECK_FALSE(json(nan) < json(1));
|
CHECK_FALSE(json(nan) < json(1));
|
||||||
CHECK_FALSE(json(1u) == json(nan));
|
CHECK_FALSE(json(1u) == json(nan));
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
CHECK((max_int64 <=> two_63) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((two_63 <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((below_two_63 <=> max_int64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((max_uint64 <=> two_64) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((json(1) <=> json(1.0)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
CHECK((json(1) <=> json(nan)) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("compares unordered")
|
SECTION("compares unordered")
|
||||||
@@ -505,6 +618,72 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
SECTION("comparison: 3-way")
|
||||||
|
{
|
||||||
|
std::vector<std::vector<std::partial_ordering>> expected =
|
||||||
|
{
|
||||||
|
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||||
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0
|
||||||
|
{eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1
|
||||||
|
{gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2
|
||||||
|
{gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3
|
||||||
|
{gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4
|
||||||
|
{gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5
|
||||||
|
{gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6
|
||||||
|
{gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7
|
||||||
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8
|
||||||
|
{gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11
|
||||||
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12
|
||||||
|
{gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18
|
||||||
|
{gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19
|
||||||
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20
|
||||||
|
{un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21
|
||||||
|
};
|
||||||
|
|
||||||
|
// check expected partial_ordering against expected booleans
|
||||||
|
REQUIRE(expected.size() == expected_eq.size());
|
||||||
|
REQUIRE(expected.size() == expected_lt.size());
|
||||||
|
for (size_t i = 0; i < expected.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == expected_eq[i].size());
|
||||||
|
REQUIRE(expected[i].size() == expected_lt[i].size());
|
||||||
|
for (size_t j = 0; j < expected[i].size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]);
|
||||||
|
CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]);
|
||||||
|
if (std::is_gt(expected[i][j]))
|
||||||
|
{
|
||||||
|
CHECK((!expected_eq[i][j] && !expected_lt[i][j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that two values compare according to their expected ordering
|
||||||
|
REQUIRE(expected.size() == j_values.size());
|
||||||
|
for (size_t i = 0; i < j_values.size(); ++i)
|
||||||
|
{
|
||||||
|
REQUIRE(expected[i].size() == j_values.size());
|
||||||
|
for (size_t j = 0; j < j_values.size(); ++j)
|
||||||
|
{
|
||||||
|
CAPTURE(i)
|
||||||
|
CAPTURE(j)
|
||||||
|
CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
@@ -543,7 +722,60 @@ TEST_CASE("lexicographical comparison operators")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace unit_comparison_detail
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
|
||||||
|
TEST_CASE("regression #3868 - heterogeneous comparisons compile under C++20 (P2468R2)")
|
||||||
|
{
|
||||||
|
// Issue #3868: operator!= was preventing compiler from synthesizing reversed
|
||||||
|
// operator== candidates under C++20's P2468R2 rewritten candidate rules.
|
||||||
|
// Verify that heterogeneous comparisons now work.
|
||||||
|
|
||||||
|
SECTION("string vs json")
|
||||||
|
{
|
||||||
|
std::string s = "string";
|
||||||
|
json j = "string";
|
||||||
|
CHECK(s == j);
|
||||||
|
CHECK(j == s);
|
||||||
|
CHECK_FALSE(s != j);
|
||||||
|
CHECK_FALSE(j != s);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("other heterogeneous types")
|
||||||
|
{
|
||||||
|
int i = 42;
|
||||||
|
json j = 42;
|
||||||
|
CHECK(i == j);
|
||||||
|
CHECK(j == i);
|
||||||
|
CHECK_FALSE(i != j);
|
||||||
|
CHECK_FALSE(j != i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
|
TEST_CASE("regression #5665 - scalar <= discarded and scalar >= discarded in C++20 legacy mode")
|
||||||
|
{
|
||||||
|
// Issue #5665: with a scalar on the left-hand side, <= and >= only had the
|
||||||
|
// candidate rewritten from operator<=>, which does not emulate the legacy
|
||||||
|
// discarded-value behavior. Check that scalar-on-the-left now matches the
|
||||||
|
// other three operand orders.
|
||||||
|
const json discarded(json::value_t::discarded);
|
||||||
|
const json one = 1;
|
||||||
|
|
||||||
|
CHECK(discarded <= 1);
|
||||||
|
CHECK(discarded >= 1);
|
||||||
|
CHECK(one <= discarded);
|
||||||
|
CHECK(one >= discarded);
|
||||||
|
CHECK(1 <= discarded);
|
||||||
|
CHECK(1 >= discarded);
|
||||||
|
CHECK(1.5 <= discarded);
|
||||||
|
CHECK(1.5 >= discarded);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
{
|
{
|
||||||
// orders keys ascending or descending, as chosen when a map is created
|
// orders keys ascending or descending, as chosen when a map is created
|
||||||
template<class Key>
|
template<class Key>
|
||||||
@@ -595,6 +827,29 @@ struct unordered_object_t : std::map<Key, Value, directed_less<Key>, Allocator>
|
|||||||
};
|
};
|
||||||
using unordered_json = nlohmann::json::with_object_t<unordered_object_t>;
|
using unordered_json = nlohmann::json::with_object_t<unordered_object_t>;
|
||||||
|
|
||||||
|
// the entries "0" to "9", enumerated in ascending or in descending order
|
||||||
|
unordered_json make_unordered_object(const bool descending)
|
||||||
|
{
|
||||||
|
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
||||||
|
for (int i = 0; i < 10; ++i)
|
||||||
|
{
|
||||||
|
j[std::to_string(i)] = i;
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Json>
|
||||||
|
Json nest(Json j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
Json outer = Json::object();
|
||||||
|
outer["x"] = std::move(j);
|
||||||
|
j = std::move(outer);
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
// a std::map comparator with state: case-insensitive, unless constructed
|
// a std::map comparator with state: case-insensitive, unless constructed
|
||||||
// case-sensitive. Used to check that copying an object copies the original's
|
// case-sensitive. Used to check that copying an object copies the original's
|
||||||
// comparator rather than default-constructing a new one (see #5649).
|
// comparator rather than default-constructing a new one (see #5649).
|
||||||
@@ -623,6 +878,18 @@ template<class Key, class Value, class /*Compare*/, class Allocator>
|
|||||||
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
using key_case_map = std::map<Key, Value, key_case_less, Allocator>;
|
||||||
using key_case_json = nlohmann::json::with_object_t<key_case_map>;
|
using key_case_json = nlohmann::json::with_object_t<key_case_map>;
|
||||||
|
|
||||||
|
// the innermost value of a chain of single-element arrays
|
||||||
|
template<typename Json>
|
||||||
|
const Json& innermost(const Json& j)
|
||||||
|
{
|
||||||
|
const Json* p = &j;
|
||||||
|
while (p->is_array())
|
||||||
|
{
|
||||||
|
p = &(*p)[0];
|
||||||
|
}
|
||||||
|
return *p;
|
||||||
|
}
|
||||||
|
|
||||||
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
// orders keys case-insensitively, so "key" and "KEY" compare equivalent
|
||||||
// (neither less than the other) although they are not equal
|
// (neither less than the other) although they are not equal
|
||||||
struct case_insensitive_less
|
struct case_insensitive_less
|
||||||
@@ -640,58 +907,7 @@ struct case_insensitive_less
|
|||||||
template<class Key, class Value, class /*Compare*/, class Allocator>
|
template<class Key, class Value, class /*Compare*/, class Allocator>
|
||||||
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
using case_insensitive_map = std::map<Key, Value, case_insensitive_less, Allocator>;
|
||||||
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
using ci_json = nlohmann::json::with_object_t<case_insensitive_map>;
|
||||||
|
|
||||||
// the types above keep external linkage: a basic_json specialized with a type
|
|
||||||
// of an anonymous namespace has internal linkage, which GCC reports in a file
|
|
||||||
// #include-d into a batch (-Wsubobject-linkage); the functions need none
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// the entries "0" to "9", enumerated in ascending or in descending order
|
|
||||||
unordered_json make_unordered_object(const bool descending)
|
|
||||||
{
|
|
||||||
unordered_json j = unordered_json::object_t(directed_less<std::string>(descending));
|
|
||||||
for (int i = 0; i < 10; ++i)
|
|
||||||
{
|
|
||||||
j[std::to_string(i)] = i;
|
|
||||||
}
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
Json nest(Json j, const std::size_t depth)
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < depth; ++i)
|
|
||||||
{
|
|
||||||
Json outer = Json::object();
|
|
||||||
outer["x"] = std::move(j);
|
|
||||||
j = std::move(outer);
|
|
||||||
}
|
|
||||||
return j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// the innermost value of a chain of single-element arrays
|
|
||||||
template<typename Json>
|
|
||||||
const Json& innermost(const Json& j)
|
|
||||||
{
|
|
||||||
const Json* p = &j;
|
|
||||||
while (p->is_array())
|
|
||||||
{
|
|
||||||
p = &(*p)[0];
|
|
||||||
}
|
|
||||||
return *p;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace unit_comparison_detail
|
|
||||||
|
|
||||||
// using-declarations rather than a using-directive, as this file may be
|
|
||||||
// #include-d into a batch with other test files (JSON_TestUnityBuild)
|
|
||||||
using unit_comparison_detail::ci_json;
|
|
||||||
using unit_comparison_detail::innermost;
|
|
||||||
using unit_comparison_detail::key_case_json;
|
|
||||||
using unit_comparison_detail::key_case_less;
|
|
||||||
using unit_comparison_detail::make_unordered_object;
|
|
||||||
using unit_comparison_detail::nest;
|
|
||||||
using unit_comparison_detail::unordered_json;
|
|
||||||
|
|
||||||
TEST_CASE("equality of objects whose entries have no fixed order")
|
TEST_CASE("equality of objects whose entries have no fixed order")
|
||||||
{
|
{
|
||||||
@@ -834,6 +1050,12 @@ TEST_CASE("containers are compared element by element")
|
|||||||
CHECK(a < b);
|
CHECK(a < b);
|
||||||
CHECK(b > a);
|
CHECK(b > a);
|
||||||
CHECK_FALSE(b < a);
|
CHECK_FALSE(b < a);
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
CHECK((a <=> b) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((b <=> a) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((a <=> a) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// a container that is a prefix of the other one
|
// a container that is a prefix of the other one
|
||||||
@@ -851,6 +1073,11 @@ TEST_CASE("containers are compared element by element")
|
|||||||
CHECK(smaller_object < larger_object);
|
CHECK(smaller_object < larger_object);
|
||||||
CHECK(larger_object > smaller_object);
|
CHECK(larger_object > smaller_object);
|
||||||
CHECK_FALSE(smaller_object == larger_object);
|
CHECK_FALSE(smaller_object == larger_object);
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
CHECK((shorter <=> longer) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((longer <=> shorter) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// elements that cannot be ordered
|
// elements that cannot be ordered
|
||||||
@@ -861,7 +1088,13 @@ TEST_CASE("containers are compared element by element")
|
|||||||
|
|
||||||
CHECK_FALSE(lhs == lhs);
|
CHECK_FALSE(lhs == lhs);
|
||||||
CHECK_FALSE(rhs < lhs);
|
CHECK_FALSE(rhs < lhs);
|
||||||
#if !JSON_HAS_THREE_WAY_COMPARISON
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
// operator<=> stops there, as std::lexicographical_compare_three_way
|
||||||
|
// does, and operator< is derived from it
|
||||||
|
CHECK((lhs <=> rhs) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK_FALSE(lhs < rhs);
|
||||||
|
#else
|
||||||
// operator< skips a pair of elements that cannot be ordered, as
|
// operator< skips a pair of elements that cannot be ordered, as
|
||||||
// std::lexicographical_compare does, and the next pair decides
|
// std::lexicographical_compare does, and the next pair decides
|
||||||
CHECK(lhs < rhs);
|
CHECK(lhs < rhs);
|
||||||
@@ -870,6 +1103,54 @@ TEST_CASE("containers are compared element by element")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
|
||||||
|
TEST_CASE("operator<=> of binary values with a different subtype does not depend on nesting depth")
|
||||||
|
{
|
||||||
|
// #5654: std::vector<std::uint8_t>::operator<=>, which the binary type's
|
||||||
|
// own operator<=> uses, ignores the subtype that operator== checks. So a
|
||||||
|
// pair of binary values with the same bytes but a different subtype is
|
||||||
|
// unequal, yet <=>-equivalent - the same inconsistency between == and <=>
|
||||||
|
// that a NaN has. Within the nesting bound, an array compares itself
|
||||||
|
// with std::vector's own operator<=>, which treats an equivalent pair as
|
||||||
|
// undecided and lets the next element decide, same as
|
||||||
|
// std::lexicographical_compare_three_way does. Past the bound,
|
||||||
|
// compare_iteratively<true>() takes over and must classify the pair the
|
||||||
|
// same way, or the result of operator<=> - and of <, which C++20 derives
|
||||||
|
// from it - depends on how deeply the values are nested.
|
||||||
|
const json a = json::array({json::binary({1}, 1), 1});
|
||||||
|
const json b = json::array({json::binary({1}, 2), 2});
|
||||||
|
|
||||||
|
// the root inconsistency: unequal, yet <=>-equivalent
|
||||||
|
CHECK_FALSE(a[0] == b[0]);
|
||||||
|
CHECK((a[0] <=> b[0]) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
|
||||||
|
const auto deep = [](const json & j, const std::size_t depth)
|
||||||
|
{
|
||||||
|
json result = j;
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
result = json::array({std::move(result)});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 127 levels stay within nesting_depth_limit() (128); 128 and 200 do not,
|
||||||
|
// and must still agree with the levels that do
|
||||||
|
for (const std::size_t depth : std::vector<std::size_t> {0, 127, 128, 200})
|
||||||
|
{
|
||||||
|
CAPTURE(depth)
|
||||||
|
const json x = deep(a, depth);
|
||||||
|
const json y = deep(b, depth);
|
||||||
|
CHECK((x <=> y) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((y <=> x) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK(x < y);
|
||||||
|
CHECK(y > x);
|
||||||
|
CHECK_FALSE(y < x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_CASE("comparison of scalars (inline fast path)")
|
TEST_CASE("comparison of scalars (inline fast path)")
|
||||||
{
|
{
|
||||||
const json::number_integer_t neg = -1;
|
const json::number_integer_t neg = -1;
|
||||||
@@ -968,6 +1249,17 @@ TEST_CASE("comparison of scalars (inline fast path)")
|
|||||||
CHECK_FALSE(nan < json(1u));
|
CHECK_FALSE(nan < json(1u));
|
||||||
CHECK_FALSE(json(1u) < nan);
|
CHECK_FALSE(json(1u) < nan);
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
||||||
|
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||||
|
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("discarded")
|
SECTION("discarded")
|
||||||
@@ -983,6 +1275,11 @@ TEST_CASE("comparison of scalars (inline fast path)")
|
|||||||
CHECK_FALSE(d == json(nullptr));
|
CHECK_FALSE(d == json(nullptr));
|
||||||
CHECK_FALSE(json(nullptr) == d);
|
CHECK_FALSE(json(nullptr) == d);
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("scalar overloads")
|
SECTION("scalar overloads")
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-conversions1.cpp (conversions to and from
|
|
||||||
// std::string_view). It is kept in a separate translation unit so the (much larger) unit-
|
|
||||||
// conversions1.cpp is built for C++11 only and not rebuilt for every C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
|
||||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
|
||||||
#define JSON_HAS_CPP_17
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("value conversion (C++17)")
|
|
||||||
{
|
|
||||||
SECTION("get a string (explicit)")
|
|
||||||
{
|
|
||||||
const json::string_t s_reference{"Hello world"};
|
|
||||||
json j(s_reference);
|
|
||||||
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string_view const s = j.get<std::string_view>();
|
|
||||||
CHECK(json(s) == j);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("exception in case of a non-string type using string_view")
|
|
||||||
{
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("get a string (explicit, get_to)")
|
|
||||||
{
|
|
||||||
const json::string_t s_reference{"Hello world"};
|
|
||||||
json j(s_reference);
|
|
||||||
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string const s = "previous value";
|
|
||||||
std::string_view sv = s;
|
|
||||||
j.get_to(sv);
|
|
||||||
CHECK(json(sv) == j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
|
||||||
SECTION("get a string (implicit)")
|
|
||||||
{
|
|
||||||
const json::string_t s_reference{"Hello world"};
|
|
||||||
json j(s_reference);
|
|
||||||
|
|
||||||
SECTION("std::string_view")
|
|
||||||
{
|
|
||||||
std::string_view const s = j.get<std::string_view>();
|
|
||||||
CHECK(json(s) == j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -6,11 +6,12 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
// When using macros that are only defined for particular versions of the standard
|
||||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||||
// go into a separate file unit-conversions1-cpp<NN>.cpp. This file stays C++11-only.
|
// version macro in a comment close by, like this:
|
||||||
|
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -30,6 +31,25 @@ using nlohmann::json;
|
|||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||||
|
#define JSON_HAS_CPP_17
|
||||||
|
#define JSON_HAS_CPP_14
|
||||||
|
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||||
|
#define JSON_HAS_CPP_14
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
#include <string_view>
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_CASE("value conversion")
|
TEST_CASE("value conversion")
|
||||||
{
|
{
|
||||||
SECTION("get an object (explicit)")
|
SECTION("get an object (explicit)")
|
||||||
@@ -499,6 +519,13 @@ TEST_CASE("value conversion")
|
|||||||
const std::string s = j.get<std::string>();
|
const std::string s = j.get<std::string>();
|
||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string_view const s = j.get<std::string_view>();
|
||||||
|
CHECK(json(s) == j);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("exception in case of a non-string type")
|
SECTION("exception in case of a non-string type")
|
||||||
{
|
{
|
||||||
@@ -524,6 +551,26 @@ TEST_CASE("value conversion")
|
|||||||
json(json::value_t::number_float).get<json::string_t>(),
|
json(json::value_t::number_float).get<json::string_t>(),
|
||||||
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
SECTION("exception in case of a non-string type using string_view")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::null).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::object).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is object", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::array).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is array", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::boolean).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is boolean", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_integer).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_unsigned).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(json(json::value_t::number_float).get<std::string_view>(),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is number", json::type_error&);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get a string (explicit, get_to)")
|
SECTION("get a string (explicit, get_to)")
|
||||||
@@ -544,6 +591,15 @@ TEST_CASE("value conversion")
|
|||||||
j.get_to(s);
|
j.get_to(s);
|
||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string const s = "previous value";
|
||||||
|
std::string_view sv = s;
|
||||||
|
j.get_to(sv);
|
||||||
|
CHECK(json(sv) == j);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("get null (explicit)")
|
SECTION("get null (explicit)")
|
||||||
@@ -582,6 +638,14 @@ TEST_CASE("value conversion")
|
|||||||
CHECK(json(s) == j);
|
CHECK(json(s) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
SECTION("std::string_view")
|
||||||
|
{
|
||||||
|
std::string_view const s = j.get<std::string_view>();
|
||||||
|
CHECK(json(s) == j);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("std::string")
|
SECTION("std::string")
|
||||||
{
|
{
|
||||||
const std::string s = j;
|
const std::string s = j;
|
||||||
@@ -1192,4 +1256,11 @@ TEST_CASE("value conversion")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#undef JSON_HAS_CPP_17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_14
|
||||||
|
#undef JSON_HAS_CPP_14
|
||||||
|
#endif
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -1,225 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-conversions2.cpp (conversions of
|
|
||||||
// std::filesystem::path, std::u8string and std::optional). It is kept in a separate
|
|
||||||
// translation unit so the (much larger) unit-conversions2.cpp is built for C++11 only and
|
|
||||||
// not rebuilt for every C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
using nlohmann::json;
|
|
||||||
|
|
||||||
// workaround for MSVC, which does not set __cplusplus to the language version (#464)
|
|
||||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
|
||||||
#define JSON_HAS_CPP_17
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NLOHMANN_JSON_SERIALIZE_ENUM uses a static std::pair
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <map>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <type_traits>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#if __has_include(<optional>)
|
|
||||||
#include <optional>
|
|
||||||
#elif __has_include(<experimental/optional>)
|
|
||||||
#include <experimental/optional>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
|
||||||
TEST_CASE("std::filesystem::path")
|
|
||||||
{
|
|
||||||
SECTION("ascii")
|
|
||||||
{
|
|
||||||
json const j_string = "Path";
|
|
||||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
|
||||||
json const j_path = p;
|
|
||||||
|
|
||||||
CHECK(j_path.template get<std::string>() ==
|
|
||||||
j_string.template get<std::string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("utf-8")
|
|
||||||
{
|
|
||||||
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
|
||||||
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
|
||||||
json const j_path = p;
|
|
||||||
|
|
||||||
CHECK(j_path.template get<std::string>() ==
|
|
||||||
j_string.template get<std::string>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// the ADL to_json overload for std::u8string only exists under the same guard
|
|
||||||
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
|
||||||
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
|
||||||
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
|
||||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
|
||||||
#if defined(__cpp_lib_char8_t)
|
|
||||||
TEST_CASE("std::u8string")
|
|
||||||
{
|
|
||||||
SECTION("ascii")
|
|
||||||
{
|
|
||||||
const std::u8string s = u8"Path";
|
|
||||||
json const j = s;
|
|
||||||
|
|
||||||
CHECK(j.template get<std::string>() == "Path");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("utf-8")
|
|
||||||
{
|
|
||||||
// use \u universal-character-names (rather than raw \x byte escapes
|
|
||||||
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
|
||||||
// encoding -- MSVC treats \x escapes used that way inside a u8
|
|
||||||
// literal as a nonstandard extension (warning C5321), which some of
|
|
||||||
// our CI configs promote to an error; \u is portable and produces
|
|
||||||
// the exact same encoded bytes without depending on the source
|
|
||||||
// file's encoding
|
|
||||||
const std::u8string s = u8"P\u011B\u0161ina";
|
|
||||||
json const j = s;
|
|
||||||
|
|
||||||
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a type whose to_json reports an error by throwing, used below to check that
|
|
||||||
// converting a std::optional<T> to JSON propagates an exception thrown while
|
|
||||||
// converting its contained value instead of calling std::terminate (#5642)
|
|
||||||
struct throwing_to_json_type {};
|
|
||||||
|
|
||||||
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
#endif
|
|
||||||
|
|
||||||
TEST_CASE("std::optional")
|
|
||||||
{
|
|
||||||
SECTION("null")
|
|
||||||
{
|
|
||||||
const json j_null;
|
|
||||||
const std::optional<std::string> opt_null;
|
|
||||||
|
|
||||||
CHECK(json(opt_null) == j_null);
|
|
||||||
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
|
||||||
|
|
||||||
// Constructing std::optional<T> directly from JSON null throws because
|
|
||||||
// std::optional's own converting constructor is chosen over basic_json's
|
|
||||||
// operator T(). This is a language-level limitation (std::optional<T> is
|
|
||||||
// constructible from T, and T is constructible from basic_json via the
|
|
||||||
// operator); there is no SFINAE path that distinguishes "call from inside
|
|
||||||
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
|
||||||
// or get_to() instead for correct null handling. See #4864 and #5246.
|
|
||||||
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
|
||||||
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
|
||||||
|
|
||||||
// Assignment goes through the same overload resolution as direct
|
|
||||||
// construction, so it throws for the same reason. This relies on
|
|
||||||
// basic_json's implicit conversion operator, so it only applies
|
|
||||||
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
|
||||||
#if JSON_USE_IMPLICIT_CONVERSIONS
|
|
||||||
std::optional<std::string> opt_assign;
|
|
||||||
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
|
||||||
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
|
||||||
std::optional<std::string> opt_get_to = "placeholder";
|
|
||||||
j_null.get_to(opt_get_to);
|
|
||||||
CHECK(opt_get_to == std::nullopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("string")
|
|
||||||
{
|
|
||||||
json j_string = "string";
|
|
||||||
std::optional<std::string> opt_string = "string";
|
|
||||||
|
|
||||||
CHECK(json(opt_string) == j_string);
|
|
||||||
CHECK(std::optional<std::string>(j_string) == opt_string);
|
|
||||||
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
|
||||||
// @infer-ignore USE_AFTER_DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("bool")
|
|
||||||
{
|
|
||||||
json j_bool = true;
|
|
||||||
std::optional<bool> opt_bool = true;
|
|
||||||
|
|
||||||
CHECK(json(opt_bool) == j_bool);
|
|
||||||
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("number")
|
|
||||||
{
|
|
||||||
json j_number = 1;
|
|
||||||
std::optional<int> opt_int = 1;
|
|
||||||
|
|
||||||
CHECK(json(opt_int) == j_number);
|
|
||||||
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("array")
|
|
||||||
{
|
|
||||||
json j_array = {1, 2, nullptr};
|
|
||||||
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
|
||||||
|
|
||||||
CHECK(json(opt_array) == j_array);
|
|
||||||
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("object")
|
|
||||||
{
|
|
||||||
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
|
||||||
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
|
||||||
|
|
||||||
CHECK(json(opt_object) == j_object);
|
|
||||||
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
|
||||||
SECTION("exception from contained value's to_json propagates (#5642)")
|
|
||||||
{
|
|
||||||
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
|
||||||
// noexcept: it calls T's to_json, which may throw (a user-defined
|
|
||||||
// to_json that reports an error, or std::bad_alloc for T =
|
|
||||||
// std::string/vector/json). Before the fix, this called
|
|
||||||
// std::terminate() instead of letting the exception propagate.
|
|
||||||
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
|
||||||
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
|
||||||
|
|
||||||
// the conversion is noexcept exactly when converting the contained value is
|
|
||||||
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
|
||||||
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
|
||||||
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
|
||||||
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
|
||||||
#endif
|
|
||||||
@@ -6,11 +6,12 @@
|
|||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// cmake/test.cmake builds a unit test with C++ standards beyond C++11 only if the
|
// cmake/test.cmake selects the C++ standard versions with which to build a
|
||||||
// source file mentions the corresponding version macro. To avoid rebuilding this
|
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
|
||||||
// large file for every standard, tests that depend on the standard version (e.g.,
|
// When using macros that are only defined for particular versions of the standard
|
||||||
// those using JSON_HAS_FILESYSTEM, JSON_HAS_RANGES, or JSON_HAS_THREE_WAY_COMPARISON)
|
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
|
||||||
// go into a separate file unit-conversions2-cpp<NN>.cpp. This file stays C++11-only.
|
// version macro in a comment close by, like this:
|
||||||
|
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
#include "doctest_compatibility.h"
|
||||||
|
|
||||||
@@ -35,6 +36,25 @@ using nlohmann::json;
|
|||||||
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
DOCTEST_CLANG_SUPPRESS_WARNING_PUSH
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
DOCTEST_CLANG_SUPPRESS_WARNING("-Wexit-time-destructors")
|
||||||
|
|
||||||
|
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464
|
||||||
|
#define JSON_HAS_CPP_17
|
||||||
|
#define JSON_HAS_CPP_14
|
||||||
|
#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)
|
||||||
|
#define JSON_HAS_CPP_14
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#if __has_include(<optional>)
|
||||||
|
#include <optional>
|
||||||
|
#elif __has_include(<experimental/optional>)
|
||||||
|
#include <experimental/optional>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(JSON_HAS_CPP_17)
|
||||||
|
#include <string_view>
|
||||||
|
#endif
|
||||||
|
|
||||||
TEST_CASE("value conversion")
|
TEST_CASE("value conversion")
|
||||||
{
|
{
|
||||||
SECTION("get a binary value (explicit)")
|
SECTION("get a binary value (explicit)")
|
||||||
@@ -660,4 +680,194 @@ TEST_CASE("Strict JSON to enum mapping")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||||
|
TEST_CASE("std::filesystem::path")
|
||||||
|
{
|
||||||
|
SECTION("ascii")
|
||||||
|
{
|
||||||
|
json const j_string = "Path";
|
||||||
|
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||||
|
json const j_path = p;
|
||||||
|
|
||||||
|
CHECK(j_path.template get<std::string>() ==
|
||||||
|
j_string.template get<std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("utf-8")
|
||||||
|
{
|
||||||
|
json const j_string = "P\xc4\x9b\xc5\xa1ina";
|
||||||
|
auto p = j_string.template get<nlohmann::detail::std_fs::path>();
|
||||||
|
json const j_path = p;
|
||||||
|
|
||||||
|
CHECK(j_path.template get<std::string>() ==
|
||||||
|
j_string.template get<std::string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// the ADL to_json overload for std::u8string only exists under the same guard
|
||||||
|
// as std::filesystem::path support (it is otherwise only reached indirectly,
|
||||||
|
// via std::filesystem::path::u8string()) -- mirror both #if conditions from
|
||||||
|
// include/nlohmann/detail/conversions/to_json.hpp exactly
|
||||||
|
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||||
|
#if defined(__cpp_lib_char8_t)
|
||||||
|
TEST_CASE("std::u8string")
|
||||||
|
{
|
||||||
|
SECTION("ascii")
|
||||||
|
{
|
||||||
|
const std::u8string s = u8"Path";
|
||||||
|
json const j = s;
|
||||||
|
|
||||||
|
CHECK(j.template get<std::string>() == "Path");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("utf-8")
|
||||||
|
{
|
||||||
|
// use \u universal-character-names (rather than raw \x byte escapes
|
||||||
|
// or literal non-ASCII source bytes) to compose the multi-byte UTF-8
|
||||||
|
// encoding -- MSVC treats \x escapes used that way inside a u8
|
||||||
|
// literal as a nonstandard extension (warning C5321), which some of
|
||||||
|
// our CI configs promote to an error; \u is portable and produces
|
||||||
|
// the exact same encoded bytes without depending on the source
|
||||||
|
// file's encoding
|
||||||
|
const std::u8string s = u8"P\u011B\u0161ina";
|
||||||
|
json const j = s;
|
||||||
|
|
||||||
|
CHECK(j.template get<std::string>() == "P\xc4\x9b\xc5\xa1ina");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a type whose to_json reports an error by throwing, used below to check that
|
||||||
|
// converting a std::optional<T> to JSON propagates an exception thrown while
|
||||||
|
// converting its contained value instead of calling std::terminate (#5642)
|
||||||
|
struct throwing_to_json_type {};
|
||||||
|
|
||||||
|
[[noreturn]] void to_json(json& /*unused*/, const throwing_to_json_type& /*unused*/)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("cannot serialize throwing_to_json_type");
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("std::optional")
|
||||||
|
{
|
||||||
|
SECTION("null")
|
||||||
|
{
|
||||||
|
const json j_null;
|
||||||
|
const std::optional<std::string> opt_null;
|
||||||
|
|
||||||
|
CHECK(json(opt_null) == j_null);
|
||||||
|
CHECK(j_null.get<std::optional<std::string>>() == std::nullopt);
|
||||||
|
|
||||||
|
// Constructing std::optional<T> directly from JSON null throws because
|
||||||
|
// std::optional's own converting constructor is chosen over basic_json's
|
||||||
|
// operator T(). This is a language-level limitation (std::optional<T> is
|
||||||
|
// constructible from T, and T is constructible from basic_json via the
|
||||||
|
// operator); there is no SFINAE path that distinguishes "call from inside
|
||||||
|
// std::optional's constructor" from "direct call". Use get<std::optional<T>>()
|
||||||
|
// or get_to() instead for correct null handling. See #4864 and #5246.
|
||||||
|
CHECK_THROWS_WITH_AS(std::optional<std::string>(j_null),
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
|
||||||
|
"[json.exception.type_error.302] type must be number, but is null", json::type_error&);
|
||||||
|
|
||||||
|
// Assignment goes through the same overload resolution as direct
|
||||||
|
// construction, so it throws for the same reason. This relies on
|
||||||
|
// basic_json's implicit conversion operator, so it only applies
|
||||||
|
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
|
||||||
|
#if JSON_USE_IMPLICIT_CONVERSIONS
|
||||||
|
std::optional<std::string> opt_assign;
|
||||||
|
CHECK_THROWS_WITH_AS(opt_assign = j_null,
|
||||||
|
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// get_to() is the correct way to obtain std::nullopt from a JSON null.
|
||||||
|
std::optional<std::string> opt_get_to = "placeholder";
|
||||||
|
j_null.get_to(opt_get_to);
|
||||||
|
CHECK(opt_get_to == std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("string")
|
||||||
|
{
|
||||||
|
json j_string = "string";
|
||||||
|
std::optional<std::string> opt_string = "string";
|
||||||
|
|
||||||
|
CHECK(json(opt_string) == j_string);
|
||||||
|
CHECK(std::optional<std::string>(j_string) == opt_string);
|
||||||
|
// false positive: Infer attributes the destruction of the temporaries above to opt_string
|
||||||
|
// @infer-ignore USE_AFTER_DELETE
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("bool")
|
||||||
|
{
|
||||||
|
json j_bool = true;
|
||||||
|
std::optional<bool> opt_bool = true;
|
||||||
|
|
||||||
|
CHECK(json(opt_bool) == j_bool);
|
||||||
|
CHECK(std::optional<bool>(j_bool) == opt_bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("number")
|
||||||
|
{
|
||||||
|
json j_number = 1;
|
||||||
|
std::optional<int> opt_int = 1;
|
||||||
|
|
||||||
|
CHECK(json(opt_int) == j_number);
|
||||||
|
CHECK(j_number.get<std::optional<int>>() == opt_int);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array")
|
||||||
|
{
|
||||||
|
json j_array = {1, 2, nullptr};
|
||||||
|
std::vector<std::optional<int>> opt_array = {{1, 2, std::nullopt}};
|
||||||
|
|
||||||
|
CHECK(json(opt_array) == j_array);
|
||||||
|
CHECK(j_array.get<std::vector<std::optional<int>>>() == opt_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("object")
|
||||||
|
{
|
||||||
|
json j_object = {{"one", 1}, {"two", 2}, {"zero", nullptr}};
|
||||||
|
std::map<std::string, std::optional<int>> opt_object {{"one", 1}, {"two", 2}, {"zero", std::nullopt}};
|
||||||
|
|
||||||
|
CHECK(json(opt_object) == j_object);
|
||||||
|
CHECK(std::map<std::string, std::optional<int>>(j_object) == opt_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("exception from contained value's to_json propagates (#5642)")
|
||||||
|
{
|
||||||
|
// to_json(BasicJsonType&, const std::optional<T>&) must not be
|
||||||
|
// noexcept: it calls T's to_json, which may throw (a user-defined
|
||||||
|
// to_json that reports an error, or std::bad_alloc for T =
|
||||||
|
// std::string/vector/json). Before the fix, this called
|
||||||
|
// std::terminate() instead of letting the exception propagate.
|
||||||
|
const std::optional<throwing_to_json_type> opt = throwing_to_json_type{};
|
||||||
|
CHECK_THROWS_WITH_AS(json(opt), "cannot serialize throwing_to_json_type", std::runtime_error&);
|
||||||
|
|
||||||
|
// the conversion is noexcept exactly when converting the contained value is
|
||||||
|
// (except with MSVC 2017, where it is never noexcept, see to_json.hpp)
|
||||||
|
#if !defined(_MSC_VER) || defined(__clang__) || _MSC_VER >= 1920
|
||||||
|
static_assert(!std::is_nothrow_constructible<json, const std::optional<throwing_to_json_type>&>::value);
|
||||||
|
static_assert(std::is_nothrow_constructible<json, const std::optional<int>&>::value);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_17
|
||||||
|
#undef JSON_HAS_CPP_17
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef JSON_HAS_CPP_14
|
||||||
|
#undef JSON_HAS_CPP_14
|
||||||
|
#endif
|
||||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||||
@@ -44,10 +44,10 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
{
|
{
|
||||||
SECTION("type int")
|
SECTION("type int")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<int>;
|
using json = json_with_metadata<int>;
|
||||||
json_t null;
|
json null;
|
||||||
auto obj = json_t::object();
|
auto obj = json::object();
|
||||||
auto array = json_t::array();
|
auto array = json::array();
|
||||||
|
|
||||||
null.metadata() = 1;
|
null.metadata() = 1;
|
||||||
obj.metadata() = 2;
|
obj.metadata() = 2;
|
||||||
@@ -61,8 +61,8 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("type vector<int>")
|
SECTION("type vector<int>")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
auto copy = value;
|
auto copy = value;
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
@@ -75,12 +75,12 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("copy ctor")
|
SECTION("copy ctor")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
|
|
||||||
json_t copy = value;
|
json copy = value;
|
||||||
|
|
||||||
CHECK(copy.metadata().size() == 2);
|
CHECK(copy.metadata().size() == 2);
|
||||||
CHECK(copy.metadata().at(0) == 1);
|
CHECK(copy.metadata().at(0) == 1);
|
||||||
@@ -95,12 +95,12 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("move ctor")
|
SECTION("move ctor")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
|
|
||||||
const json_t moved = std::move(value);
|
const json moved = std::move(value);
|
||||||
|
|
||||||
CHECK(moved.metadata().size() == 2);
|
CHECK(moved.metadata().size() == 2);
|
||||||
CHECK(moved.metadata().at(0) == 1);
|
CHECK(moved.metadata().at(0) == 1);
|
||||||
@@ -108,12 +108,12 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("move assign")
|
SECTION("move assign")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
|
|
||||||
json_t moved;
|
json moved;
|
||||||
moved = std::move(value);
|
moved = std::move(value);
|
||||||
|
|
||||||
CHECK(moved.metadata().size() == 2);
|
CHECK(moved.metadata().size() == 2);
|
||||||
@@ -122,12 +122,12 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("copy assign")
|
SECTION("copy assign")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
|
|
||||||
json_t copy;
|
json copy;
|
||||||
copy = value;
|
copy = value;
|
||||||
|
|
||||||
CHECK(copy.metadata().size() == 2);
|
CHECK(copy.metadata().size() == 2);
|
||||||
@@ -143,8 +143,8 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("type unique_ptr<int>")
|
SECTION("type unique_ptr<int>")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::unique_ptr<int>>;
|
using json = json_with_metadata<std::unique_ptr<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
|
value.metadata().reset(new int(42)); // NOLINT(cppcoreguidelines-owning-memory)
|
||||||
auto moved = std::move(value);
|
auto moved = std::move(value);
|
||||||
|
|
||||||
@@ -153,12 +153,12 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("type vector<int> in json array")
|
SECTION("type vector<int> in json array")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<std::vector<int>>;
|
using json = json_with_metadata<std::vector<int>>;
|
||||||
json_t value;
|
json value;
|
||||||
value.metadata().emplace_back(1);
|
value.metadata().emplace_back(1);
|
||||||
value.metadata().emplace_back(2);
|
value.metadata().emplace_back(2);
|
||||||
|
|
||||||
json_t const array(10, value);
|
json const array(10, value);
|
||||||
|
|
||||||
CHECK(value.metadata().size() == 2);
|
CHECK(value.metadata().size() == 2);
|
||||||
CHECK(value.metadata().at(0) == 1);
|
CHECK(value.metadata().at(0) == 1);
|
||||||
@@ -173,10 +173,10 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("member swap")
|
SECTION("member swap")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<int>;
|
using json = json_with_metadata<int>;
|
||||||
json_t a = 1;
|
json a = 1;
|
||||||
a.metadata() = 100;
|
a.metadata() = 100;
|
||||||
json_t b = 2;
|
json b = 2;
|
||||||
b.metadata() = 200;
|
b.metadata() = 200;
|
||||||
|
|
||||||
a.swap(b);
|
a.swap(b);
|
||||||
@@ -188,10 +188,10 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("nonmember swap")
|
SECTION("nonmember swap")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<int>;
|
using json = json_with_metadata<int>;
|
||||||
json_t a = 1;
|
json a = 1;
|
||||||
a.metadata() = 100;
|
a.metadata() = 100;
|
||||||
json_t b = 2;
|
json b = 2;
|
||||||
b.metadata() = 200;
|
b.metadata() = 200;
|
||||||
|
|
||||||
using std::swap;
|
using std::swap;
|
||||||
@@ -204,10 +204,10 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
}
|
}
|
||||||
SECTION("std::swap")
|
SECTION("std::swap")
|
||||||
{
|
{
|
||||||
using json_t = json_with_metadata<int>;
|
using json = json_with_metadata<int>;
|
||||||
json_t a = 1;
|
json a = 1;
|
||||||
a.metadata() = 100;
|
a.metadata() = 100;
|
||||||
json_t b = 2;
|
json b = 2;
|
||||||
b.metadata() = 200;
|
b.metadata() = 200;
|
||||||
|
|
||||||
std::swap(a, b);
|
std::swap(a, b);
|
||||||
@@ -221,15 +221,15 @@ TEST_CASE("JSON Node Metadata")
|
|||||||
{
|
{
|
||||||
// std::sort mixes swap() with moves; each value's metadata must
|
// std::sort mixes swap() with moves; each value's metadata must
|
||||||
// travel with it, just as it does for copy, move, and assignment
|
// travel with it, just as it does for copy, move, and assignment
|
||||||
using json_t = json_with_metadata<int>;
|
using json = json_with_metadata<int>;
|
||||||
std::vector<json_t> values;
|
std::vector<json> values;
|
||||||
for (const int v :
|
for (const int v :
|
||||||
{
|
{
|
||||||
5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10,
|
5, 3, 9, 1, 7, 2, 8, 4, 6, 0, 15, 13, 19, 11, 17, 12, 18, 14, 16, 10,
|
||||||
25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33
|
25, 23, 29, 21, 27, 22, 28, 24, 26, 20, 35, 33
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
json_t value = v;
|
json value = v;
|
||||||
value.metadata() = v;
|
value.metadata() = v;
|
||||||
values.push_back(value);
|
values.push_back(value);
|
||||||
}
|
}
|
||||||
@@ -267,19 +267,19 @@ template <class Ptr, class Fnc>
|
|||||||
void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
||||||
{
|
{
|
||||||
using value_t = nlohmann::detail::value_t;
|
using value_t = nlohmann::detail::value_t;
|
||||||
const json_with_visitor_t& self = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
const json_with_visitor_t& json = *static_cast<const json_with_visitor_t*>(this); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
|
||||||
switch (self.type())
|
switch (json.type())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
for (const auto& entry : self.items())
|
for (const auto& entry : json.items())
|
||||||
{
|
{
|
||||||
entry.value().do_visit(ptr / entry.key(), fnc);
|
entry.value().do_visit(ptr / entry.key(), fnc);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::array:
|
case value_t::array:
|
||||||
for (std::size_t i = 0; i < self.size(); ++i)
|
for (std::size_t i = 0; i < json.size(); ++i)
|
||||||
{
|
{
|
||||||
self.at(i).do_visit(ptr / std::to_string(i), fnc);
|
json.at(i).do_visit(ptr / std::to_string(i), fnc);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
@@ -292,22 +292,22 @@ void visitor_adaptor::do_visit(const Ptr& ptr, const Fnc& fnc) const
|
|||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
default:
|
default:
|
||||||
fnc(ptr, self);
|
fnc(ptr, json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("JSON Visit Node")
|
TEST_CASE("JSON Visit Node")
|
||||||
{
|
{
|
||||||
json_with_visitor_t root;
|
json_with_visitor_t json;
|
||||||
root["null"];
|
json["null"];
|
||||||
root["int"] = -1;
|
json["int"] = -1;
|
||||||
root["uint"] = 1U;
|
json["uint"] = 1U;
|
||||||
root["float"] = 1.0;
|
json["float"] = 1.0;
|
||||||
root["boolean"] = true;
|
json["boolean"] = true;
|
||||||
root["string"] = "string";
|
json["string"] = "string";
|
||||||
root["array"].push_back(0);
|
json["array"].push_back(0);
|
||||||
root["array"].push_back(1);
|
json["array"].push_back(1);
|
||||||
root["array"].push_back(root);
|
json["array"].push_back(json);
|
||||||
|
|
||||||
std::set<std::string> expected
|
std::set<std::string> expected
|
||||||
{
|
{
|
||||||
@@ -330,7 +330,7 @@ TEST_CASE("JSON Visit Node")
|
|||||||
"/array/2/array/1 - number_integer - 1"
|
"/array/2/array/1 - number_integer - 1"
|
||||||
};
|
};
|
||||||
|
|
||||||
root.visit(
|
json.visit(
|
||||||
[&](const json_with_visitor_t::json_pointer & p,
|
[&](const json_with_visitor_t::json_pointer & p,
|
||||||
const json_with_visitor_t& j)
|
const json_with_visitor_t& j)
|
||||||
{
|
{
|
||||||
@@ -374,7 +374,7 @@ TEST_CASE("JSON Visit Node")
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
str << " - " << j.dump();
|
str << " - " << j.dump();
|
||||||
CHECK(root.at(p) == j);
|
CHECK(json.at(p) == j);
|
||||||
INFO(str.str());
|
INFO(str.str());
|
||||||
CHECK(expected.count(str.str()) == 1);
|
CHECK(expected.count(str.str()) == 1);
|
||||||
expected.erase(str.str());
|
expected.erase(str.str());
|
||||||
@@ -404,22 +404,22 @@ using json_with_hidden_base_members = nlohmann::json::with_base_class_t<base_cla
|
|||||||
|
|
||||||
TEST_CASE("JSON Node as_base_class")
|
TEST_CASE("JSON Node as_base_class")
|
||||||
{
|
{
|
||||||
using json_t = json_with_hidden_base_members;
|
using json = json_with_hidden_base_members;
|
||||||
|
|
||||||
static_assert(std::is_same<decltype(std::declval<json_t&>().as_base_class()), json_t::json_base_class_t&>::value, "");
|
static_assert(std::is_same<decltype(std::declval<json&>().as_base_class()), json::json_base_class_t&>::value, "");
|
||||||
static_assert(std::is_same<decltype(std::declval<const json_t&>().as_base_class()), const json_t::json_base_class_t&>::value, "");
|
static_assert(std::is_same<decltype(std::declval<const json&>().as_base_class()), const json::json_base_class_t&>::value, "");
|
||||||
static_assert(noexcept(std::declval<json_t&>().as_base_class()), "");
|
static_assert(noexcept(std::declval<json&>().as_base_class()), "");
|
||||||
static_assert(noexcept(std::declval<const json_t&>().as_base_class()), "");
|
static_assert(noexcept(std::declval<const json&>().as_base_class()), "");
|
||||||
|
|
||||||
SECTION("non-const")
|
SECTION("non-const")
|
||||||
{
|
{
|
||||||
json_t j = {1, 2, 3};
|
json j = {1, 2, 3};
|
||||||
|
|
||||||
CHECK(std::string(j.type_name()) == "array");
|
CHECK(std::string(j.type_name()) == "array");
|
||||||
CHECK(j.size() == 3);
|
CHECK(j.size() == 3);
|
||||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||||
CHECK(j.as_base_class().size() == 42);
|
CHECK(j.as_base_class().size() == 42);
|
||||||
CHECK(&j.as_base_class() == &static_cast<json_t::json_base_class_t&>(j));
|
CHECK(&j.as_base_class() == &static_cast<json::json_base_class_t&>(j));
|
||||||
|
|
||||||
j.as_base_class().m_size = 7;
|
j.as_base_class().m_size = 7;
|
||||||
CHECK(j.as_base_class().size() == 7);
|
CHECK(j.as_base_class().size() == 7);
|
||||||
@@ -428,13 +428,13 @@ TEST_CASE("JSON Node as_base_class")
|
|||||||
|
|
||||||
SECTION("const")
|
SECTION("const")
|
||||||
{
|
{
|
||||||
const json_t j = {1, 2, 3};
|
const json j = {1, 2, 3};
|
||||||
|
|
||||||
CHECK(std::string(j.type_name()) == "array");
|
CHECK(std::string(j.type_name()) == "array");
|
||||||
CHECK(j.size() == 3);
|
CHECK(j.size() == 3);
|
||||||
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
CHECK(std::string(j.as_base_class().type_name()) == "custom type_name");
|
||||||
CHECK(j.as_base_class().size() == 42);
|
CHECK(j.as_base_class().size() == 42);
|
||||||
CHECK(&j.as_base_class() == &static_cast<const json_t::json_base_class_t&>(j));
|
CHECK(&j.as_base_class() == &static_cast<const json::json_base_class_t&>(j));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
// __ _____ _____ _____
|
|
||||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
|
||||||
// | | |__ | | | | | | version 3.12.0
|
|
||||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
|
||||||
//
|
|
||||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
// This file contains the C++17-only part of unit-custom-binary-type.cpp (binary types
|
|
||||||
// whose value type is std::byte). It is kept in a separate translation unit so the (much
|
|
||||||
// larger) unit-custom-binary-type.cpp is built for C++11 only and not rebuilt for every
|
|
||||||
// C++ standard.
|
|
||||||
|
|
||||||
#include "doctest_compatibility.h"
|
|
||||||
|
|
||||||
// capture whether JSON_DISABLE_ENUM_SERIALIZATION was enabled on the command
|
|
||||||
// line before including json.hpp, which #undefs it; std::byte is an enum, so
|
|
||||||
// converting std::vector<std::byte> needs enum serialization
|
|
||||||
#if defined(JSON_DISABLE_ENUM_SERIALIZATION) && (JSON_DISABLE_ENUM_SERIALIZATION == 1)
|
|
||||||
#define SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#ifdef JSON_HAS_CPP_17
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// a BinaryType whose value type is not an integer type at all
|
|
||||||
using byte_binary_json = nlohmann::json::with_binary_t<std::vector<std::byte>>;
|
|
||||||
|
|
||||||
TEST_CASE("binary type whose value type is not std::uint8_t (C++17)")
|
|
||||||
{
|
|
||||||
SECTION("dumping a value type that is not an integer")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
|
||||||
CHECK(byte_binary_json::binary(bytes).dump() == R"({"bytes":[0,1,255],"subtype":null})");
|
|
||||||
CHECK(byte_binary_json::binary(bytes, 42).dump() == R"({"bytes":[0,1,255],"subtype":42})");
|
|
||||||
CHECK(byte_binary_json::binary({}).dump() == R"({"bytes":[],"subtype":null})");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("hashing and the binary formats")
|
|
||||||
{
|
|
||||||
const std::vector<std::byte> bytes{std::byte{0}, std::byte{1}, std::byte{0xFF}};
|
|
||||||
const auto j = byte_binary_json::binary(bytes);
|
|
||||||
|
|
||||||
CHECK(std::hash<byte_binary_json> {}(j) == std::hash<byte_binary_json> {}(j));
|
|
||||||
CHECK(byte_binary_json::from_cbor(byte_binary_json::to_cbor(j)) == j);
|
|
||||||
CHECK(byte_binary_json::from_msgpack(byte_binary_json::to_msgpack(j)) == j);
|
|
||||||
|
|
||||||
// UBJSON has no binary type, so binary values are written as an array
|
|
||||||
CHECK(byte_binary_json::from_ubjson(byte_binary_json::to_ubjson(j)) == byte_binary_json({0, 1, 255}));
|
|
||||||
// the same holds for BON8
|
|
||||||
CHECK(byte_binary_json::from_bon8(byte_binary_json::to_bon8(j)) == byte_binary_json({0, 1, 255}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// moved from unit-regression3.cpp, so that file does not instantiate a second
|
|
||||||
// basic_json specialization (MinGW section limit, see windows.yml)
|
|
||||||
TEST_CASE("std::vector<std::byte> as binary type")
|
|
||||||
{
|
|
||||||
SECTION("issue #4804: from_cbor incompatible with std::vector<std::byte> as binary_t")
|
|
||||||
{
|
|
||||||
const std::vector<std::uint8_t> data = {0x80};
|
|
||||||
const auto decoded = byte_binary_json::from_cbor(data);
|
|
||||||
CHECK((decoded == byte_binary_json::array()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef SKIP_TESTS_FOR_ENUM_SERIALIZATION
|
|
||||||
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
|
|
||||||
{
|
|
||||||
// Test that assigning a custom BinaryType directly creates a binary value, not an array
|
|
||||||
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
|
|
||||||
const byte_binary_json j = original;
|
|
||||||
CHECK(j.is_binary());
|
|
||||||
CHECK(!j.is_array());
|
|
||||||
|
|
||||||
// Test round-tripping: extracting the binary value back as the custom container type
|
|
||||||
const auto extracted = j.get<std::vector<std::byte>>();
|
|
||||||
CHECK(extracted == original);
|
|
||||||
|
|
||||||
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
|
|
||||||
const nlohmann::json default_json = std::vector<std::uint8_t> {1, 2, 3};
|
|
||||||
CHECK(default_json.is_array());
|
|
||||||
CHECK(!default_json.is_binary());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
|
|
||||||
{
|
|
||||||
// Test that extracting a custom BinaryType from a parsed JSON array still works
|
|
||||||
// (not just from a binary-typed node)
|
|
||||||
const auto j = byte_binary_json::parse("[1,2,3]");
|
|
||||||
CHECK(j.is_array());
|
|
||||||
CHECK(!j.is_binary());
|
|
||||||
|
|
||||||
// Extracting as custom BinaryType should work from arrays
|
|
||||||
const auto extracted = j.get<std::vector<std::byte>>();
|
|
||||||
CHECK(extracted.size() == 3);
|
|
||||||
CHECK(extracted[0] == std::byte{1});
|
|
||||||
CHECK(extracted[1] == std::byte{2});
|
|
||||||
CHECK(extracted[2] == std::byte{3});
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user