mirror of
https://github.com/nlohmann/json.git
synced 2026-09-11 18:57:58 +00:00
Compare commits
117
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f42af2d9f6 | ||
|
|
cc2d83f6fd | ||
|
|
dc77b1c9da | ||
|
|
134c34b48f | ||
|
|
d4fd0041cf | ||
|
|
7eea0c4ea4 | ||
|
|
cffe0129af | ||
|
|
fc4be88570 | ||
|
|
768a24ce9a | ||
|
|
aa391dc0a5 | ||
|
|
d2514a46f7 | ||
|
|
0452641c18 | ||
|
|
a14619b354 | ||
|
|
91ab3e81f5 | ||
|
|
0dd8ca9023 | ||
|
|
d9c55eb225 | ||
|
|
44f8ec30e9 | ||
|
|
4bb2de8cc5 | ||
|
|
dd50f0eb16 | ||
|
|
fae364db24 | ||
|
|
1dc1d09fc6 | ||
|
|
471584616f | ||
|
|
da42a627dc | ||
|
|
ff80ed3295 | ||
|
|
35cdd5f408 | ||
|
|
bfb07786cd | ||
|
|
4a93aa4e2f | ||
|
|
d69fb8654d | ||
|
|
3e2de32226 | ||
|
|
b17c272f46 | ||
|
|
8e26b9d4b3 | ||
|
|
8c8d14cb4e | ||
|
|
d22c5c7641 | ||
|
|
c9f1d2d646 | ||
|
|
a316738cfa | ||
|
|
8c38e270b5 | ||
|
|
c9477ccf91 | ||
|
|
d6660cf718 | ||
|
|
c05347dd54 | ||
|
|
6d86cc0f6b | ||
|
|
faa35cc647 | ||
|
|
7b2d73cf2e | ||
|
|
09b6b6b5ba | ||
|
|
dd26d3ff07 | ||
|
|
3c875683a5 | ||
|
|
137a40b9aa | ||
|
|
19386dd14a | ||
|
|
c864bb36da | ||
|
|
129d2891ed | ||
|
|
35705d79d8 | ||
|
|
892be68ca4 | ||
|
|
1ac268d409 | ||
|
|
3fa93dac65 | ||
|
|
1876493f87 | ||
|
|
01853ed6bc | ||
|
|
2f025f401e | ||
|
|
734fd305a1 | ||
|
|
36187cacfb | ||
|
|
b5378e8deb | ||
|
|
ce87157d4e | ||
|
|
cdf52ae9be | ||
|
|
146ba55453 | ||
|
|
e6978ba50c | ||
|
|
6285225fd0 | ||
|
|
21af527e75 | ||
|
|
23518f54fe | ||
|
|
1c136a66c4 | ||
|
|
c1c19a7bcd | ||
|
|
bacdabd176 | ||
|
|
d5647e6a3b | ||
|
|
9a091d2b82 | ||
|
|
b890b4cba3 | ||
|
|
dca9d49a33 | ||
|
|
acd87e2336 | ||
|
|
ad94fb01cc | ||
|
|
c2e1cc50e0 | ||
|
|
173f2a7407 | ||
|
|
1c63a120b6 | ||
|
|
85889e8843 | ||
|
|
3c0a9a99fd | ||
|
|
e82724d87f | ||
|
|
78821cd9c2 | ||
|
|
68f0722a19 | ||
|
|
5f121d8c50 | ||
|
|
585929bff9 | ||
|
|
31ba5208c8 | ||
|
|
2222d386c9 | ||
|
|
eaedec859a | ||
|
|
d94cbd99dc | ||
|
|
bc48951128 | ||
|
|
fd72ecfc8c | ||
|
|
de8a099ba5 | ||
|
|
dd24e2dffd | ||
|
|
868506dcc0 | ||
|
|
58ce09dcfd | ||
|
|
8dacb98041 | ||
|
|
2e23687092 | ||
|
|
227c5cdfb1 | ||
|
|
0832fd1cb4 | ||
|
|
8ec98e2c9e | ||
|
|
88b28ac43c | ||
|
|
e0c3c819e1 | ||
|
|
06ac77f4fd | ||
|
|
dfa51af692 | ||
|
|
d0d29039da | ||
|
|
9a3ebb9456 | ||
|
|
3296a3ad8c | ||
|
|
3565f40229 | ||
|
|
1c5a953de5 | ||
|
|
a03e65420c | ||
|
|
d6ede37088 | ||
|
|
722c03495f | ||
|
|
c197feff81 | ||
|
|
b2b47c69b1 | ||
|
|
6a406ee141 | ||
|
|
ca76c37650 | ||
|
|
fe2bcc080f |
@@ -108,7 +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-regression2.cpp` and add a section referencing the fixed issue.
|
When fixing a bug, edit `unit-regression3.cpp` and add a section referencing the fixed issue.
|
||||||
|
`unit-regression2.cpp` holds the older tests; the two files exist because a single one grew large enough for the
|
||||||
|
MinGW linker to fail relocating it, so please keep adding to the smaller file rather than growing the larger one.
|
||||||
|
|
||||||
#### Exceptions
|
#### Exceptions
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ guidance.
|
|||||||
|
|
||||||
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
||||||
|
|
||||||
|
## Unofficial packages
|
||||||
|
|
||||||
|
This project does not publish an official npm package. The npm package
|
||||||
|
[`nlohmann-json`](https://www.npmjs.com/package/nlohmann-json) (or similarly named packages) is not maintained or
|
||||||
|
endorsed by this project. See the
|
||||||
|
[package managers documentation](https://json.nlohmann.me/integration/package_managers/#npm) for supported
|
||||||
|
integration options.
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
|
|
||||||
- Explore security-related topics and contribute to tools and projects through
|
- Explore security-related topics and contribute to tools and projects through
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -34,19 +34,19 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout pull request
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
path: main
|
path: main
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout tools
|
- name: Checkout tools
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
path: tools
|
path: tools
|
||||||
ref: develop
|
ref: develop
|
||||||
@@ -67,8 +67,18 @@ jobs:
|
|||||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||||
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
|
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
|
||||||
|
|
||||||
|
# fail loudly if a directory is renamed or removed: find would only warn
|
||||||
|
# about the missing path and silently drop its files from the check
|
||||||
|
SOURCE_DIRS="docs/mkdocs/docs/examples include tests"
|
||||||
|
for DIR in $SOURCE_DIRS; do
|
||||||
|
if [ ! -d "$DIR" ]; then
|
||||||
|
echo "::error::source directory '$DIR' does not exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||||
$(find docs/examples include tests -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
|
$(find $SOURCE_DIRS -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
|
||||||
|
|
||||||
- name: Build patch and check for differences
|
- name: Build patch and check for differences
|
||||||
id: diff
|
id: diff
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -27,25 +27,25 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -43,6 +43,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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
standard: [11, 14, 17, 20, 23, 26]
|
standard: [11, 14, 17, 20, 23, 26]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ on:
|
|||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- docs/mkdocs/**
|
- docs/mkdocs/**
|
||||||
- docs/examples/**
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
|
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
|
||||||
@@ -27,11 +26,11 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
|
|
||||||
- name: Install virtual environment
|
- name: Install virtual environment
|
||||||
run: make install_venv -C docs/mkdocs
|
run: make install_venv -C docs/mkdocs
|
||||||
|
|||||||
@@ -36,17 +36,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: sarif
|
results_format: sarif
|
||||||
@@ -76,6 +76,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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -32,12 +32,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
# Checkout project source
|
# Checkout project source
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -61,7 +61,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@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||||
with:
|
with:
|
||||||
stale-issue-label: 'state: stale'
|
stale-issue-label: 'state: stale'
|
||||||
stale-pr-label: 'state: stale'
|
stale-pr-label: 'state: stale'
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
@@ -43,11 +43,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
||||||
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -60,17 +60,17 @@ jobs:
|
|||||||
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Install Valgrind
|
- name: Install Valgrind
|
||||||
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -85,11 +85,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -100,15 +100,15 @@ jobs:
|
|||||||
container: ubuntu:focal
|
container: ubuntu:focal
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls]
|
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_simdutf]
|
||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -118,11 +118,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install dependencies and de_DE locale
|
- name: Install dependencies and de_DE locale
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
name: code-coverage-report
|
name: code-coverage-report
|
||||||
path: ${{ github.workspace }}/build/html
|
path: ${{ github.workspace }}/build/html
|
||||||
- name: Publish report to Coveralls
|
- name: Publish report to Coveralls
|
||||||
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
|
uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
|
||||||
@@ -163,7 +163,15 @@ jobs:
|
|||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
# add-apt-repository resolves the PPA through the Launchpad API,
|
||||||
|
# which intermittently times out or fails the team lookup (the plain
|
||||||
|
# "deb ..." sources below never hit Launchpad and never flake).
|
||||||
|
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
||||||
|
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
||||||
|
sleep $((attempt * 10))
|
||||||
|
done
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||||
@@ -172,11 +180,11 @@ jobs:
|
|||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -190,11 +198,11 @@ jobs:
|
|||||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||||
container: gcc:${{ matrix.compiler }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -204,14 +212,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', 'latest']
|
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||||
container: silkeh/clang:${{ matrix.compiler }}
|
container: silkeh/clang:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
||||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||||
@@ -227,11 +235,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
standard: [11, 14, 17, 20, 23, 26]
|
standard: [11, 14, 17, 20, 23, 26]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -247,11 +255,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install git and unzip
|
- name: Install git and unzip
|
||||||
run: apt-get update ; apt-get install -y git unzip
|
run: apt-get update ; apt-get install -y git unzip
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build with libc++
|
- name: Build with libc++
|
||||||
@@ -274,11 +282,11 @@ jobs:
|
|||||||
cuda: ['11.8.0', '12.1.1', '12.6.3']
|
cuda: ['11.8.0', '12.1.1', '12.6.3']
|
||||||
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04
|
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -291,14 +299,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ${{ matrix.container }}
|
container: ${{ matrix.container }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# The module test uses `import std;`, which needs CMake's experimental
|
# The module test uses `import std;`, which needs CMake's experimental
|
||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||||
@@ -320,11 +328,11 @@ jobs:
|
|||||||
# Intel's own last officially published image that still includes it.
|
# Intel's own last officially published image that still includes it.
|
||||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -337,9 +345,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -349,9 +357,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -361,17 +369,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Install emscripten
|
- name: Install emscripten
|
||||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -384,11 +392,11 @@ jobs:
|
|||||||
target: [ci_test_examples, ci_test_build_documentation]
|
target: [ci_test_examples, ci_test_build_documentation]
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
|
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
architecture: [x64, x86]
|
architecture: [x64, x86]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up MinGW
|
- name: Set up MinGW
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
@@ -86,9 +86,9 @@ jobs:
|
|||||||
runs-on: windows-2025
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
||||||
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
runs-on: windows-11-arm
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Run CMake (Release)
|
- name: Run CMake (Release)
|
||||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||||
if: matrix.build_type == 'Release'
|
if: matrix.build_type == 'Release'
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7, 20.1.8]
|
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7, 20.1.8]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install Clang
|
- name: Install Clang
|
||||||
@@ -153,10 +153,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
platform: x64
|
platform: x64
|
||||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||||
|
# CMAKE_CXX_FLAGS_DEBUG is overridden to drop the default -g: linking
|
||||||
|
# test-regression2_cpp20 intermittently fails with "relocation truncated
|
||||||
|
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
||||||
|
# MinGW linker cannot relocate the debug sections this test produces.
|
||||||
|
# The tests are only built and run here, so the debug info is not used.
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build ^
|
run: cmake -S . -B build ^
|
||||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||||
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
|
||||||
|
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
|
||||||
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
|
||||||
-G"MinGW Makefiles" ^
|
-G"MinGW Makefiles" ^
|
||||||
-DCMAKE_BUILD_TYPE=Debug ^
|
-DCMAKE_BUILD_TYPE=Debug ^
|
||||||
@@ -173,7 +179,7 @@ jobs:
|
|||||||
architecture: [Win32, x64]
|
architecture: [Win32, x64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -186,14 +192,14 @@ jobs:
|
|||||||
ci_module_cpp20:
|
ci_module_cpp20:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# The module test uses `import std;`, which needs CMake's experimental
|
# The module test uses `import std;`, which needs CMake's experimental
|
||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
- name: Run CMake (Debug)
|
- name: Run CMake (Debug)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: pretty clean ChangeLog.md release
|
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# configuration
|
# configuration
|
||||||
@@ -41,6 +41,8 @@ all:
|
|||||||
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
@echo "fuzz_testing_ubjson - prepare fuzz testing of the UBJSON parser"
|
||||||
@echo "pretty - beautify code with Artistic Style"
|
@echo "pretty - beautify code with Artistic Style"
|
||||||
@echo "run_benchmarks - build and run benchmarks"
|
@echo "run_benchmarks - build and run benchmarks"
|
||||||
|
@echo "update_hedley - download Hedley and regenerate hedley.hpp / hedley_undef.hpp"
|
||||||
|
@echo "update_hedley_undef - rebuild hedley_undef.hpp from the JSON_HEDLEY_* #define names in hedley.hpp"
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
@@ -241,11 +243,24 @@ update_hedley:
|
|||||||
rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
rm -f include/nlohmann/thirdparty/hedley/hedley.hpp include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
||||||
curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp
|
curl https://raw.githubusercontent.com/nemequ/hedley/master/hedley.h -o include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
$(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp
|
$(SED) -i 's/HEDLEY_/JSON_HEDLEY_/g' include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
grep "[[:blank:]]*#[[:blank:]]*undef" include/nlohmann/thirdparty/hedley/hedley.hpp | grep -v "__" | sort | uniq | $(SED) 's/ //g' | $(SED) 's/undef/undef /g' > include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
|
||||||
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp
|
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
$(SED) -i '1s/^/#pragma once\n\n/' include/nlohmann/thirdparty/hedley/hedley_undef.hpp
|
$(MAKE) update_hedley_undef
|
||||||
$(MAKE) amalgamate
|
$(MAKE) amalgamate
|
||||||
|
|
||||||
|
# Rebuild hedley_undef.hpp from every JSON_HEDLEY_* name that hedley.hpp
|
||||||
|
# #defines. Hedley does not #undef all of its public macros internally (see
|
||||||
|
# #5408), so grepping those #undef lines misses names such as
|
||||||
|
# JSON_HEDLEY_PRAGMA. cmake/scripts/gen_hedley_undef_check.cmake is the
|
||||||
|
# single source of truth for this extraction (tests/CMakeLists.txt uses the
|
||||||
|
# same script, in MODE=checks, to generate the matching leak-check test), so
|
||||||
|
# the vendored header, the generated #undef list, and the regression test
|
||||||
|
# cannot drift apart.
|
||||||
|
update_hedley_undef:
|
||||||
|
cmake -DHEDLEY_HPP=include/nlohmann/thirdparty/hedley/hedley.hpp \
|
||||||
|
-DOUTPUT=include/nlohmann/thirdparty/hedley/hedley_undef.hpp \
|
||||||
|
-DMODE=undef \
|
||||||
|
-P cmake/scripts/gen_hedley_undef_check.cmake
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# serve_header.py
|
# serve_header.py
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
- [Specializing enum conversion](#specializing-enum-conversion)
|
- [Specializing enum conversion](#specializing-enum-conversion)
|
||||||
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
|
||||||
- [Customers](#customers)
|
- [Customers](#customers)
|
||||||
|
- [Ecosystem](#ecosystem)
|
||||||
- [Supported compilers](#supported-compilers)
|
- [Supported compilers](#supported-compilers)
|
||||||
- [Integration](#integration)
|
- [Integration](#integration)
|
||||||
- [CMake](#cmake)
|
- [CMake](#cmake)
|
||||||
@@ -90,7 +91,6 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl
|
|||||||
- [Steve Sperandeo](https://github.com/homer6)
|
- [Steve Sperandeo](https://github.com/homer6)
|
||||||
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
||||||
- [Steve Wagner](https://github.com/ciroque)
|
- [Steve Wagner](https://github.com/ciroque)
|
||||||
- [Lion Yang](https://github.com/LionNatsu)
|
|
||||||
|
|
||||||
### Further support
|
### Further support
|
||||||
|
|
||||||
@@ -1187,6 +1187,11 @@ The library is used in multiple projects, applications, operating systems, etc.
|
|||||||
|
|
||||||
[](https://json.nlohmann.me/home/customers/)
|
[](https://json.nlohmann.me/home/customers/)
|
||||||
|
|
||||||
|
## Ecosystem
|
||||||
|
|
||||||
|
Beyond projects that use the library, there are third-party projects that build on top of it - schema validators,
|
||||||
|
language bindings, format converters, and the like. See the curated [Ecosystem](https://json.nlohmann.me/community/ecosystem/) page.
|
||||||
|
|
||||||
## Supported compilers
|
## Supported compilers
|
||||||
|
|
||||||
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
|
||||||
@@ -1802,13 +1807,13 @@ The library itself consists of a single header file licensed under the MIT licen
|
|||||||
- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
|
- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
|
||||||
- [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing
|
- [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing
|
||||||
- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
|
- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
|
||||||
- [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code indentation
|
- [**Artistic Style**](https://astyle.sourceforge.net) for automatic source code indentation
|
||||||
- [**Clang**](https://clang.llvm.org) for compilation with code sanitizers
|
- [**Clang**](https://clang.llvm.org) for compilation with code sanitizers
|
||||||
- [**CMake**](https://cmake.org) for build automation
|
- [**CMake**](https://cmake.org) for build automation
|
||||||
- [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard)
|
- [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard)
|
||||||
- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
|
- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
|
||||||
- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
|
- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
|
||||||
- [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis
|
- [**cppcheck**](https://cppcheck.sourceforge.io) for static analysis
|
||||||
- [**doctest**](https://github.com/onqtam/doctest) for the unit tests
|
- [**doctest**](https://github.com/onqtam/doctest) for the unit tests
|
||||||
- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
|
||||||
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
|
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
|
||||||
@@ -1823,6 +1828,15 @@ The library itself consists of a single header file licensed under the MIT licen
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
### Standards compliance
|
||||||
|
|
||||||
|
The library targets strict conformance with [RFC 8259](https://tools.ietf.org/html/rfc8259.html). Both the original [JSONTestSuite](https://github.com/nst/JSONTestSuite) and its updated revision are exercised in CI; their test data is downloaded from [`nlohmann/json_test_data`](https://github.com/nlohmann/json_test_data) at configure time rather than committed to this repository (see [`tests/src/unit-testsuites.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-testsuites.cpp)):
|
||||||
|
|
||||||
|
- The updated revision runs all mandatory `y_` (must-accept) and `n_` (must-reject) cases through the strict [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) entry point; the original suite runs its `n_` cases through `parse()` and its `y_` cases through [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/).
|
||||||
|
- The `i_` (implementation-defined) cases are, by RFC 8259, free to be accepted *or* rejected, so "passing all `i_` cases" is not a meaningful conformance metric. The library makes deliberate, documented choices there: nesting depth is not artificially limited, a leading UTF-8 byte order mark is silently ignored, [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) are forwarded unchanged, invalid UTF-8 and lone/unpaired UTF-16 surrogates are rejected (stricter than required), and a number that cannot be stored without becoming `NaN`/`INF` raises [`out_of_range.406`](https://json.nlohmann.me/home/exceptions/#jsonexceptionout_of_range406).
|
||||||
|
|
||||||
|
One behavioral nuance is worth calling out, because a superficial test often misreads it as non-compliance: [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) is strict and rejects trailing data after a value, whereas [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/) follows relaxed iostream semantics — it parses a single value and leaves the stream positioned right after it. Feeding "a valid document followed by trailing bytes" through `operator>>` reports success; the same input through `parse()` is rejected. This is a documented two-API design, not a conformance gap. See [**parsing**](https://json.nlohmann.me/features/parsing/) for details.
|
||||||
|
|
||||||
### Character encoding
|
### Character encoding
|
||||||
|
|
||||||
The library supports **Unicode input** as follows:
|
The library supports **Unicode input** as follows:
|
||||||
|
|||||||
+19
-1
@@ -212,6 +212,24 @@ add_custom_target(ci_test_legacycomparison
|
|||||||
COMMENT "Compile and test with legacy discarded value comparison enabled"
|
COMMENT "Compile and test with legacy discarded value comparison enabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Validate UTF-8 with simdutf.
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
add_custom_target(ci_test_simdutf
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
|
-DJSON_BuildTests=ON -DJSON_TestSimdutf=ON
|
||||||
|
# simdutf needs C++17, so the library falls back to its scalar validator
|
||||||
|
# below that: build the suite at C++11 to cover the fallback with the macro
|
||||||
|
# defined, and at C++17 to run every test against simdutf itself
|
||||||
|
"-DJSON_TestStandards=11\;17"
|
||||||
|
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_simdutf
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_simdutf
|
||||||
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_simdutf && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||||
|
COMMENT "Compile and test with simdutf UTF-8 validation enabled"
|
||||||
|
)
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Enable brace-init copy semantics.
|
# Enable brace-init copy semantics.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -294,7 +312,7 @@ file(GLOB_RECURSE INDENT_FILES
|
|||||||
${PROJECT_SOURCE_DIR}/tests/src/*.cpp
|
${PROJECT_SOURCE_DIR}/tests/src/*.cpp
|
||||||
${PROJECT_SOURCE_DIR}/tests/src/*.hpp
|
${PROJECT_SOURCE_DIR}/tests/src/*.hpp
|
||||||
${PROJECT_SOURCE_DIR}/tests/benchmarks/src/benchmarks.cpp
|
${PROJECT_SOURCE_DIR}/tests/benchmarks/src/benchmarks.cpp
|
||||||
${PROJECT_SOURCE_DIR}/docs/examples/*.cpp
|
${PROJECT_SOURCE_DIR}/docs/mkdocs/docs/examples/*.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(include_dir ${PROJECT_SOURCE_DIR}/single_include/nlohmann)
|
set(include_dir ${PROJECT_SOURCE_DIR}/single_include/nlohmann)
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
# -Wno-extra-semi-stmt The library uses assert which triggers this warning.
|
# -Wno-extra-semi-stmt The library uses assert which triggers this warning.
|
||||||
# -Wno-padded We do not care about padding warnings.
|
# -Wno-padded We do not care about padding warnings.
|
||||||
# -Wno-covered-switch-default All switches list all cases and a default case.
|
# -Wno-covered-switch-default All switches list all cases and a default case.
|
||||||
|
# -Wno-c2y-extensions Clang 22.1 diagnoses __COUNTER__ as a C2y extension, also in
|
||||||
|
# C++ mode. The library does not use __COUNTER__; the warnings
|
||||||
|
# all come from vendored Doctest (SECTION/TEST_CASE macros).
|
||||||
# -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code
|
# -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code
|
||||||
# (to_chars, serializer, lexer, binary reader/writer, input
|
# (to_chars, serializer, lexer, binary reader/writer, input
|
||||||
# adapters, json_pointer) plus vendored Doctest itself (~208
|
# adapters, json_pointer) plus vendored Doctest itself (~208
|
||||||
@@ -20,5 +23,6 @@ set(CLANG_CXXFLAGS
|
|||||||
-Wno-extra-semi-stmt
|
-Wno-extra-semi-stmt
|
||||||
-Wno-padded
|
-Wno-padded
|
||||||
-Wno-covered-switch-default
|
-Wno-covered-switch-default
|
||||||
|
-Wno-c2y-extensions
|
||||||
-Wno-unsafe-buffer-usage
|
-Wno-unsafe-buffer-usage
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# Shared extractor for the JSON_HEDLEY_* macro names defined in hedley.hpp.
|
||||||
|
#
|
||||||
|
# Every macro that hedley.hpp #defines must be #undef-ed again once json.hpp
|
||||||
|
# has been fully processed (see include/nlohmann/detail/macro_unscope.hpp
|
||||||
|
# and https://github.com/nlohmann/json/issues/5408). Deriving the macro list
|
||||||
|
# straight from hedley.hpp here -- instead of hand-maintaining it in two
|
||||||
|
# places -- means hedley_undef.hpp and the regression test that checks for
|
||||||
|
# leaked macros can never drift apart, even after a future `make
|
||||||
|
# update_hedley` pulls in new macros from upstream Hedley.
|
||||||
|
#
|
||||||
|
# MODE=undef (default): write hedley_undef.hpp (SPDX header, #pragma once,
|
||||||
|
# one #undef per macro name) -- used by `make update_hedley_undef`
|
||||||
|
# MODE=checks: write one #ifdef/FAIL_CHECK/#endif per macro name,
|
||||||
|
# meant to be #include-d inside a TEST_CASE -- used by
|
||||||
|
# tests/CMakeLists.txt to (re)generate the include for
|
||||||
|
# tests/src/unit-no-macro-leak.cpp
|
||||||
|
#
|
||||||
|
# Required variables:
|
||||||
|
# HEDLEY_HPP path to include/nlohmann/thirdparty/hedley/hedley.hpp
|
||||||
|
# OUTPUT path of the file to (over)write
|
||||||
|
# Optional:
|
||||||
|
# MODE "undef" (default) or "checks"
|
||||||
|
|
||||||
|
if(NOT DEFINED HEDLEY_HPP OR NOT DEFINED OUTPUT)
|
||||||
|
message(FATAL_ERROR "HEDLEY_HPP and OUTPUT must be set")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT EXISTS "${HEDLEY_HPP}")
|
||||||
|
message(FATAL_ERROR "Hedley header not found: ${HEDLEY_HPP}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT DEFINED MODE)
|
||||||
|
set(MODE undef)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT MODE STREQUAL "undef" AND NOT MODE STREQUAL "checks")
|
||||||
|
message(FATAL_ERROR "MODE must be undef or checks, got: ${MODE}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Line-anchored, like `grep -oE "^[[:blank:]]*#[[:blank:]]*define[[:blank:]]+JSON_HEDLEY_[A-Za-z0-9_]+"`.
|
||||||
|
# Unanchored matching would also pick up JSON_HEDLEY_* mentions inside
|
||||||
|
# comments or string literals elsewhere in the file, which must not turn
|
||||||
|
# into #undef lines.
|
||||||
|
file(STRINGS "${HEDLEY_HPP}" hedley_lines)
|
||||||
|
set(macro_names)
|
||||||
|
foreach(line IN LISTS hedley_lines)
|
||||||
|
if("${line}" MATCHES "^[ \t]*#[ \t]*define[ \t]+(JSON_HEDLEY_[A-Za-z0-9_]+)")
|
||||||
|
list(APPEND macro_names "${CMAKE_MATCH_1}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(NOT macro_names)
|
||||||
|
message(FATAL_ERROR "No JSON_HEDLEY_* macros found in ${HEDLEY_HPP}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
list(REMOVE_DUPLICATES macro_names)
|
||||||
|
# Lexicographic, locale-independent (ASCII-only names) -- matches `LC_ALL=C sort`.
|
||||||
|
list(SORT macro_names COMPARE STRING)
|
||||||
|
list(LENGTH macro_names macro_count)
|
||||||
|
|
||||||
|
set(generated "")
|
||||||
|
if(MODE STREQUAL "undef")
|
||||||
|
# Same banner `make update_hedley_undef` would stamp by hand, so the
|
||||||
|
# recipe is self-contained and its output is byte-stable across reruns.
|
||||||
|
# The embedded SPDX tags below are part of the *generated* file's
|
||||||
|
# content, not a REUSE header for this .cmake script itself (which is
|
||||||
|
# already covered by the blanket "Files: *" rule in .reuse/dep5) -- keep
|
||||||
|
# them wrapped in REUSE-IgnoreStart/End so `reuse lint` does not try to
|
||||||
|
# parse "MIT\n")" as this file's own SPDX-License-Identifier value.
|
||||||
|
# REUSE-IgnoreStart
|
||||||
|
string(APPEND generated "// __ _____ _____ _____\n")
|
||||||
|
string(APPEND generated "// __| | __| | | | JSON for Modern C++\n")
|
||||||
|
string(APPEND generated "// | | |__ | | | | | | version 3.12.0\n")
|
||||||
|
string(APPEND generated "// |_____|_____|_____|_|___| https://github.com/nlohmann/json\n")
|
||||||
|
string(APPEND generated "//\n")
|
||||||
|
string(APPEND generated "// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>\n")
|
||||||
|
string(APPEND generated "// SPDX-License-Identifier: MIT\n")
|
||||||
|
# REUSE-IgnoreEnd
|
||||||
|
string(APPEND generated "\n")
|
||||||
|
string(APPEND generated "#pragma once\n")
|
||||||
|
string(APPEND generated "\n")
|
||||||
|
foreach(name IN LISTS macro_names)
|
||||||
|
string(APPEND generated "#undef ${name}\n")
|
||||||
|
endforeach()
|
||||||
|
else()
|
||||||
|
string(APPEND generated "// This file is generated by cmake/scripts/gen_hedley_undef_check.cmake\n")
|
||||||
|
string(APPEND generated "// from include/nlohmann/thirdparty/hedley/hedley.hpp. Do not edit it by\n")
|
||||||
|
string(APPEND generated "// hand -- it is regenerated on every build. ${macro_count} macros checked.\n\n")
|
||||||
|
foreach(name IN LISTS macro_names)
|
||||||
|
string(APPEND generated "#ifdef ${name}\n")
|
||||||
|
string(APPEND generated " FAIL_CHECK(\"${name} leaked after including nlohmann/json.hpp\");\n")
|
||||||
|
string(APPEND generated "#endif\n")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_filename_component(output_dir "${OUTPUT}" DIRECTORY)
|
||||||
|
if(output_dir)
|
||||||
|
file(MAKE_DIRECTORY "${output_dir}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Avoid rewriting the file (and busting downstream incremental rebuilds)
|
||||||
|
# when the content has not actually changed.
|
||||||
|
set(write_output TRUE)
|
||||||
|
if(EXISTS "${OUTPUT}")
|
||||||
|
file(READ "${OUTPUT}" existing_content)
|
||||||
|
if(existing_content STREQUAL generated)
|
||||||
|
set(write_output FALSE)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
if(write_output)
|
||||||
|
file(WRITE "${OUTPUT}" "${generated}")
|
||||||
|
endif()
|
||||||
@@ -8,8 +8,8 @@ static bool accept(InputType&& i,
|
|||||||
const bool ignore_trailing_commas = false);
|
const bool ignore_trailing_commas = false);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static bool accept(IteratorType first, IteratorType last,
|
static bool accept(IteratorType first, SentinelType last,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false);
|
const bool ignore_trailing_commas = false);
|
||||||
```
|
```
|
||||||
@@ -17,10 +17,11 @@ static bool accept(IteratorType first, IteratorType last,
|
|||||||
Checks whether the input is valid JSON.
|
Checks whether the input is valid JSON.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from a pair of character iterators
|
2. Reads from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support)
|
||||||
|
|
||||||
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
||||||
respectively as UTF-8, UTF-16, and UTF-32.
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
|
the iterator type with `operator!=`.
|
||||||
|
|
||||||
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
|
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
|
||||||
(i.e., a parse error) nor creates diagnostic information.
|
(i.e., a parse error) nor creates diagnostic information.
|
||||||
@@ -44,6 +45,12 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
|
|||||||
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
|
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
|
||||||
- a pair of pointers such as `ptr` and `ptr + len`
|
- a pair of pointers such as `ptr` and `ptr + len`
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -61,7 +68,7 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
|
|||||||
: iterator to the start of the character range
|
: iterator to the start of the character range
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the character range
|
: iterator to the end of the character range, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
@@ -112,6 +119,7 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
|
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ is compatible with both of the binary data formats that use binary subtyping, (t
|
|||||||
incompatible with each other, and it is up to the user to translate between them). The subtype is added to `BinaryType`
|
incompatible with each other, and it is up to the user to translate between them). The subtype is added to `BinaryType`
|
||||||
via the helper type [byte_container_with_subtype](../byte_container_with_subtype/index.md).
|
via the helper type [byte_container_with_subtype](../byte_container_with_subtype/index.md).
|
||||||
|
|
||||||
[CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type as:
|
[CBOR's RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) describes this type as:
|
||||||
> Major type 2: a byte string. The string's length in bytes is represented following the rules for positive integers
|
> Major type 2: A byte string. The number of bytes in the string is equal to the argument.
|
||||||
> (major type 0).
|
|
||||||
|
|
||||||
[MessagePack's documentation on the bin type
|
[MessagePack's documentation on the bin type
|
||||||
family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as:
|
family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as:
|
||||||
@@ -51,6 +50,38 @@ represent a byte array in modern C++.
|
|||||||
|
|
||||||
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
|
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
|
||||||
|
|
||||||
|
#### Custom BinaryType behavior
|
||||||
|
|
||||||
|
When a custom `BinaryType` is configured (other than the default `#!cpp std::vector<std::uint8_t>`), you can assign
|
||||||
|
values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values
|
||||||
|
rather than arrays:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using custom_json = nlohmann::basic_json<
|
||||||
|
nlohmann::ordered_map, // ObjectType
|
||||||
|
std::vector, // ArrayType
|
||||||
|
std::string, // StringType
|
||||||
|
bool, // BooleanType
|
||||||
|
std::int64_t, // NumberIntegerType
|
||||||
|
std::uint64_t, // NumberUnsignedType
|
||||||
|
double, // NumberFloatType
|
||||||
|
std::allocator, // AllocatorType
|
||||||
|
nlohmann::adl_serializer,
|
||||||
|
std::vector<std::byte> // Custom BinaryType
|
||||||
|
>;
|
||||||
|
|
||||||
|
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
|
||||||
|
custom_json j = data; // Creates a binary value, not an array
|
||||||
|
assert(j.is_binary());
|
||||||
|
|
||||||
|
// Round-tripping works seamlessly
|
||||||
|
auto extracted = j.get<std::vector<std::byte>>();
|
||||||
|
assert(extracted == data);
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations.
|
||||||
|
The default `nlohmann::json` continues to treat `#!cpp std::vector<std::uint8_t>` as arrays for backward compatibility.
|
||||||
|
|
||||||
#### Storage
|
#### Storage
|
||||||
|
|
||||||
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
|
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
|
||||||
|
|||||||
@@ -43,6 +43,17 @@ Strong guarantee: if an exception is thrown, there are no changes to any JSON va
|
|||||||
Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON value
|
Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON value
|
||||||
is not UTF-8 encoded and `error_handler` is set to `strict`
|
is not UTF-8 encoded and `error_handler` is set to `strict`
|
||||||
|
|
||||||
|
!!! warning "Serializing untrusted input"
|
||||||
|
|
||||||
|
When serializing values that may contain invalid or untrusted UTF-8 (e.g., bytes taken directly from network
|
||||||
|
input), `dump()` throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) in the default
|
||||||
|
`strict` mode. To serialize such data without throwing, pass
|
||||||
|
[`error_handler_t::replace`](error_handler_t.md) (substitutes U+FFFD) or
|
||||||
|
[`error_handler_t::ignore`](error_handler_t.md). Callers that serialize untrusted input on a crash-sensitive path
|
||||||
|
should either choose a non-strict error handler or wrap `dump()` in a `#!cpp try`/`#!cpp catch`.
|
||||||
|
|
||||||
|
See the [FAQ](../../home/faq.md#serializing-untrusted-or-invalid-utf-8) for details.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear.
|
Linear.
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ static basic_json from_bjdata(InputType&& i,
|
|||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json from_bjdata(IteratorType first, IteratorType last,
|
static basic_json from_bjdata(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
```
|
```
|
||||||
@@ -16,7 +16,7 @@ static basic_json from_bjdata(IteratorType first, IteratorType last,
|
|||||||
Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format.
|
Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from an iterator range.
|
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||||
|
|
||||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md).
|
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md).
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`IteratorType`
|
`IteratorType`
|
||||||
: a compatible iterator type
|
: a compatible iterator type
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
: iterator to the start of the input
|
: iterator to the start of the input
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the input
|
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||||
@@ -103,3 +109,4 @@ Linear in the size of the input.
|
|||||||
|
|
||||||
- Added in version 3.11.0.
|
- Added in version 3.11.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ static basic_json from_bson(InputType&& i,
|
|||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json from_bson(IteratorType first, IteratorType last,
|
static basic_json from_bson(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
```
|
```
|
||||||
@@ -16,7 +16,7 @@ static basic_json from_bson(IteratorType first, IteratorType last,
|
|||||||
Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format.
|
Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from an iterator range.
|
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||||
|
|
||||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md).
|
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md).
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`IteratorType`
|
`IteratorType`
|
||||||
: a compatible iterator type
|
: a compatible iterator type
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
: iterator to the start of the input
|
: iterator to the start of the input
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the input
|
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||||
@@ -103,6 +109,7 @@ Linear in the size of the input.
|
|||||||
|
|
||||||
- Added in version 3.4.0.
|
- Added in version 3.4.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ static basic_json from_cbor(InputType&& i,
|
|||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json from_cbor(IteratorType first, IteratorType last,
|
static basic_json from_cbor(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
|
||||||
@@ -19,7 +19,7 @@ static basic_json from_cbor(IteratorType first, IteratorType last,
|
|||||||
Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format.
|
Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from an iterator range.
|
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||||
|
|
||||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md).
|
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md).
|
||||||
|
|
||||||
@@ -38,6 +38,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`IteratorType`
|
`IteratorType`
|
||||||
: a compatible iterator type
|
: a compatible iterator type
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -47,7 +53,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
: iterator to the start of the input
|
: iterator to the start of the input
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the input
|
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||||
@@ -113,6 +119,7 @@ Linear in the size of the input.
|
|||||||
- Added `allow_exceptions` parameter in version 3.2.0.
|
- Added `allow_exceptions` parameter in version 3.2.0.
|
||||||
- Added `tag_handler` parameter in version 3.9.0.
|
- Added `tag_handler` parameter in version 3.9.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ static basic_json from_msgpack(InputType&& i,
|
|||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json from_msgpack(IteratorType first, IteratorType last,
|
static basic_json from_msgpack(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
```
|
```
|
||||||
@@ -16,7 +16,7 @@ static basic_json from_msgpack(IteratorType first, IteratorType last,
|
|||||||
Deserializes a given input to a JSON value using the MessagePack serialization format.
|
Deserializes a given input to a JSON value using the MessagePack serialization format.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from an iterator range.
|
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||||
|
|
||||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md).
|
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md).
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`IteratorType`
|
`IteratorType`
|
||||||
: a compatible iterator type
|
: a compatible iterator type
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
: iterator to the start of the input
|
: iterator to the start of the input
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the input
|
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||||
@@ -105,6 +111,7 @@ Linear in the size of the input.
|
|||||||
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
|
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
|
||||||
- Added `allow_exceptions` parameter in version 3.2.0.
|
- Added `allow_exceptions` parameter in version 3.2.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ static basic_json from_ubjson(InputType&& i,
|
|||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json from_ubjson(IteratorType first, IteratorType last,
|
static basic_json from_ubjson(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true);
|
const bool allow_exceptions = true);
|
||||||
```
|
```
|
||||||
@@ -16,7 +16,7 @@ static basic_json from_ubjson(IteratorType first, IteratorType last,
|
|||||||
Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format.
|
Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format.
|
||||||
|
|
||||||
1. Reads from a compatible input.
|
1. Reads from a compatible input.
|
||||||
2. Reads from an iterator range.
|
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support).
|
||||||
|
|
||||||
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md).
|
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md).
|
||||||
|
|
||||||
@@ -35,6 +35,12 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
`IteratorType`
|
`IteratorType`
|
||||||
: a compatible iterator type
|
: a compatible iterator type
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -44,7 +50,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
: iterator to the start of the input
|
: iterator to the start of the input
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of the input
|
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
`strict` (in)
|
`strict` (in)
|
||||||
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
: whether to expect the input to be consumed until EOF (`#!cpp true` by default)
|
||||||
@@ -104,6 +110,7 @@ Linear in the size of the input.
|
|||||||
- Added in version 3.1.0.
|
- Added in version 3.1.0.
|
||||||
- Added `allow_exceptions` parameter in version 3.2.0.
|
- Added `allow_exceptions` parameter in version 3.2.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
|||||||
do not belong to the same JSON value; example: `"iterators do not fit"`
|
do not belong to the same JSON value; example: `"iterators do not fit"`
|
||||||
- Throws [`invalid_iterator.211`](../../home/exceptions.md#jsonexceptioninvalid_iterator211) if `first` or `last`
|
- Throws [`invalid_iterator.211`](../../home/exceptions.md#jsonexceptioninvalid_iterator211) if `first` or `last`
|
||||||
are iterators into container for which insert is called; example: `"passed iterators may not belong to container"`
|
are iterators into container for which insert is called; example: `"passed iterators may not belong to container"`
|
||||||
|
- Throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) if `first` or `last`
|
||||||
|
do not point to an array; example: `"iterators first and last must point to arrays"`
|
||||||
4. The function can throw the following exceptions:
|
4. The function can throw the following exceptions:
|
||||||
- Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if called on JSON values other than
|
- Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if called on JSON values other than
|
||||||
arrays; example: `"cannot use insert() with string"`
|
arrays; example: `"cannot use insert() with string"`
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ static basic_json parse(InputType&& i,
|
|||||||
const bool ignore_trailing_commas = false);
|
const bool ignore_trailing_commas = false);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
static basic_json parse(IteratorType first, IteratorType last,
|
static basic_json parse(IteratorType first, SentinelType last,
|
||||||
const parser_callback_t cb = nullptr,
|
const parser_callback_t cb = nullptr,
|
||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
@@ -19,10 +19,11 @@ static basic_json parse(IteratorType first, IteratorType last,
|
|||||||
```
|
```
|
||||||
|
|
||||||
1. Deserialize from a compatible input.
|
1. Deserialize from a compatible input.
|
||||||
2. Deserialize from a pair of character iterators
|
2. Deserialize from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support)
|
||||||
|
|
||||||
The `value_type` of the iterator must be an integral type with size of 1, 2, or 4 bytes, which will be interpreted
|
The `value_type` of the iterator must be an integral type with size of 1, 2, or 4 bytes, which will be interpreted
|
||||||
respectively as UTF-8, UTF-16, and UTF-32.
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
|
the iterator type with `operator!=`.
|
||||||
|
|
||||||
## Template parameters
|
## Template parameters
|
||||||
|
|
||||||
@@ -43,6 +44,12 @@ static basic_json parse(IteratorType first, IteratorType last,
|
|||||||
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
|
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
|
||||||
- a pair of pointers such as `ptr` and `ptr + len`
|
- a pair of pointers such as `ptr` and `ptr + len`
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
`i` (in)
|
`i` (in)
|
||||||
@@ -67,7 +74,7 @@ static basic_json parse(IteratorType first, IteratorType last,
|
|||||||
: iterator to the start of a character range
|
: iterator to the start of a character range
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of a character range
|
: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
@@ -238,6 +245,7 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
|||||||
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
|
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,14 @@ Discarding a value (i.e., returning `#!cpp false`) has different effects dependi
|
|||||||
called:
|
called:
|
||||||
|
|
||||||
- Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never
|
- Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never
|
||||||
read.
|
read. This holds for every value type and for both kinds of parent: a discarded element is removed from the
|
||||||
|
surrounding array, and a discarded member is removed from the surrounding object together with its key.
|
||||||
|
- Arrays and objects can be discarded either at their `parse_event_t::array_start`/`parse_event_t::object_start` event
|
||||||
|
or at their `parse_event_t::array_end`/`parse_event_t::object_end` event, and both remove the whole value. Discarding
|
||||||
|
it at the start event also means the callback is called neither for the content of the value nor for its matching end
|
||||||
|
event.
|
||||||
|
- Discarding a `parse_event_t::key` event discards the whole object member. The callback is still called for the
|
||||||
|
associated value, but its return value has no further effect.
|
||||||
- In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level
|
- In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level
|
||||||
element is skipped.
|
element is skipped.
|
||||||
|
|
||||||
@@ -49,7 +56,7 @@ called:
|
|||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
|
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
|
||||||
the latter case, it is either skipped completely or replaced by an empty discarded object.
|
the latter case, it is skipped completely, or replaced by `null` if it is the top-level value.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -68,6 +75,21 @@ the latter case, it is either skipped completely or replaced by an empty discard
|
|||||||
--8<-- "examples/parse__string__parser_callback_t.output"
|
--8<-- "examples/parse__string__parser_callback_t.output"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The example below shows where discarded values are removed. The array and the number are discarded in different
|
||||||
|
ways, but in each case the parse result contains neither the value nor its key.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
--8<-- "examples/parser_callback_t.cpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```json
|
||||||
|
--8<-- "examples/parser_callback_t.output"
|
||||||
|
```
|
||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
- [parse](parse.md) deserialize from a compatible input
|
- [parse](parse.md) deserialize from a compatible input
|
||||||
@@ -76,3 +98,5 @@ the latter case, it is either skipped completely or replaced by an empty discard
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 1.0.0.
|
- Added in version 1.0.0.
|
||||||
|
- Fixed in version 3.13.0 to also remove discarded values from a parent object; before, discarding an array or a value
|
||||||
|
stored under an object key left a discarded member behind, which made the parse result serialize to invalid JSON.
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
("add", "remove", "move")
|
("add", "remove", "move")
|
||||||
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
||||||
location has a parent that is neither an object nor an array.
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
|
||||||
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
|
||||||
|
location is a proper prefix of its "path" location.
|
||||||
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
||||||
unsuccessful.
|
unsuccessful.
|
||||||
|
|
||||||
@@ -75,3 +79,7 @@ is thrown. In any case, the original value is not changed: the patch is applied
|
|||||||
- Added in version 2.0.0.
|
- Added in version 2.0.0.
|
||||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||||
target location has a non-object/non-array parent in version 3.13.0.
|
target location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||||
|
location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||||
|
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ No guarantees, value may be corrupted by an unsuccessful patch operation.
|
|||||||
("add", "remove", "move")
|
("add", "remove", "move")
|
||||||
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
- Throws [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) if an "add" operation's target
|
||||||
location has a parent that is neither an object nor an array.
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) if a "remove" operation's target
|
||||||
|
location has a parent that is neither an object nor an array.
|
||||||
|
- Throws [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) if a "move" operation's "from"
|
||||||
|
location is a proper prefix of its "path" location.
|
||||||
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
- Throws [`other_error.501`](../../home/exceptions.md#jsonexceptionother_error501) if "test" operation was
|
||||||
unsuccessful.
|
unsuccessful.
|
||||||
|
|
||||||
@@ -72,3 +76,7 @@ function throws an exception.
|
|||||||
- Added in version 3.11.0.
|
- Added in version 3.11.0.
|
||||||
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
- Added [`out_of_range.411`](../../home/exceptions.md#jsonexceptionout_of_range411) and stopped relying on an internal assertion when an "add" operation's
|
||||||
target location has a non-object/non-array parent in version 3.13.0.
|
target location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.413`](../../home/exceptions.md#jsonexceptionout_of_range413) and stopped silently ignoring a "remove" operation whose target
|
||||||
|
location has a non-object/non-array parent in version 3.13.0.
|
||||||
|
- Added [`out_of_range.414`](../../home/exceptions.md#jsonexceptionout_of_range414) and rejected a "move" operation whose "from" location is a proper
|
||||||
|
prefix of its "path" location instead of silently producing a corrupted result in version 3.13.0.
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ static bool sax_parse(InputType&& i,
|
|||||||
const bool ignore_trailing_commas = false);
|
const bool ignore_trailing_commas = false);
|
||||||
|
|
||||||
// (2)
|
// (2)
|
||||||
template<class IteratorType, class SAX>
|
template<class IteratorType, class SAX, class SentinelType = IteratorType>
|
||||||
static bool sax_parse(IteratorType first, IteratorType last,
|
static bool sax_parse(IteratorType first, SentinelType last,
|
||||||
SAX* sax,
|
SAX* sax,
|
||||||
input_format_t format = input_format_t::json,
|
input_format_t format = input_format_t::json,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
@@ -23,10 +23,11 @@ static bool sax_parse(IteratorType first, IteratorType last,
|
|||||||
Read from input and generate SAX events
|
Read from input and generate SAX events
|
||||||
|
|
||||||
1. Read from a compatible input.
|
1. Read from a compatible input.
|
||||||
2. Read from a pair of character iterators
|
2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support)
|
||||||
|
|
||||||
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
|
||||||
respectively as UTF-8, UTF-16, and UTF-32.
|
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to
|
||||||
|
the iterator type with `operator!=`.
|
||||||
|
|
||||||
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
|
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
|
||||||
|
|
||||||
@@ -46,6 +47,12 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
|
||||||
with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32)
|
with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32)
|
||||||
|
|
||||||
|
`SentinelType`
|
||||||
|
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for overload (2), for instance.
|
||||||
|
|
||||||
|
- a custom sentinel type for C++20 ranges
|
||||||
|
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
|
||||||
|
|
||||||
`SAX`
|
`SAX`
|
||||||
: a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md)
|
: a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md)
|
||||||
|
|
||||||
@@ -76,7 +83,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
|||||||
: iterator to the start of a character range
|
: iterator to the start of a character range
|
||||||
|
|
||||||
`last` (in)
|
`last` (in)
|
||||||
: iterator to the end of a character range
|
: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=`
|
||||||
|
|
||||||
## Return value
|
## Return value
|
||||||
|
|
||||||
@@ -128,6 +135,7 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
- Ignoring comments via `ignore_comments` added in version 3.9.0.
|
||||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
|
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ optional, `#!cpp bjdata_version_t::draft2` by default.
|
|||||||
|
|
||||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
||||||
|
is false.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear in the size of the JSON value `j`.
|
Linear in the size of the JSON value `j`.
|
||||||
|
|||||||
@@ -40,10 +40,15 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||||
|
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
||||||
|
string, or binary value exceeds the range of the 32-bit BSON length field; example:
|
||||||
|
`"BSON length 2147483661 exceeds maximum of 2147483647"`
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear in the size of the JSON value `j`.
|
Proportional to the size of the JSON value `j` multiplied by its maximum nesting
|
||||||
|
depth, `O(n × d)`. BSON length prefixes are computed recursively before nested
|
||||||
|
values are written.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ The exact mapping and its limitations are described on a [dedicated page](../../
|
|||||||
|
|
||||||
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
Strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||||
|
|
||||||
|
## Exceptions
|
||||||
|
|
||||||
|
- Throws [`other_error.502`](../../home/exceptions.md#jsonexceptionother_error502) if `use_type` is true and `use_size`
|
||||||
|
is false.
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear in the size of the JSON value `j`.
|
Linear in the size of the JSON value `j`.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**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_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
|
||||||
|
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||||
|
|
||||||
## Library version
|
## Library version
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ When the macro is not defined, the library will define it to its default value.
|
|||||||
|
|
||||||
Diagnostic messages can also be controlled with the CMake option
|
Diagnostic messages can also be controlled with the CMake option
|
||||||
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
|
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
|
||||||
which defines `JSON_DIAGNOSTICS` accordingly.
|
which defines `JSON_DIAGNOSTICS` accordingly. Note this only applies when building the
|
||||||
|
library from source — see the pre-installed-package caveat on that page.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# JSON_USE_SIMDUTF
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_USE_SIMDUTF
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
||||||
|
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||||
|
|
||||||
|
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||||
|
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||||
|
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
|
||||||
|
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||||
|
scalar path.
|
||||||
|
|
||||||
|
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
|
||||||
|
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||||
|
|
||||||
|
!!! note "Requires C++17"
|
||||||
|
|
||||||
|
simdutf requires C++17 and its header rejects older standards with an `#!cpp #error`. The backend is therefore only
|
||||||
|
compiled in from C++17 on. In C++11 and C++14 the macro has no effect and the scalar validator is used, which
|
||||||
|
accepts and rejects exactly the same input -- only throughput differs. Setting the macro project-wide is therefore
|
||||||
|
safe even when some translation units are built with an older standard.
|
||||||
|
|
||||||
|
!!! warning "Define consistently"
|
||||||
|
|
||||||
|
The macro selects between two definitions of the same inline validation function. It must therefore be defined
|
||||||
|
identically for **every** translation unit that includes the library; mixing translation units that define it with
|
||||||
|
ones that do not is an ODR violation. Prefer setting it as a compile definition on the target rather than with
|
||||||
|
`#!cpp #define` in individual source files.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_USE_SIMDUTF` is not defined and the portable C++11 scalar validator is used.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_USE_SIMDUTF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The code below enables the simdutf backend for UTF-8 validation.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_USE_SIMDUTF 1
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The project must also link against simdutf, e.g. with CMake:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_compile_definitions(your_target PRIVATE JSON_USE_SIMDUTF)
|
||||||
|
target_link_libraries(your_target PRIVATE simdutf::simdutf)
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! hint "Testing this configuration"
|
||||||
|
|
||||||
|
The unit tests can be built against the simdutf backend with the CMake option `JSON_TestSimdutf` (`OFF` by
|
||||||
|
default), which fetches simdutf and defines `JSON_USE_SIMDUTF` for every test target. The `ci_test_simdutf` target
|
||||||
|
runs the whole test suite in that configuration.
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.13.0.
|
||||||
@@ -33,17 +33,44 @@ A UTF-8 byte order mark is silently ignored.
|
|||||||
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||||
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
||||||
|
|
||||||
`operator>>` parses exactly one JSON value and leaves the stream positioned right after it, so it can be called
|
`operator>>` parses exactly one JSON value, so it can be called repeatedly to read a sequence of concatenated JSON
|
||||||
repeatedly to read a sequence of concatenated JSON values from the same stream:
|
values from the same stream:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
json j1, j2;
|
json j1, j2;
|
||||||
input >> j1; // parses the first value, stream now positioned right after it
|
input >> j1; // parses the first value
|
||||||
input >> j2; // parses the next value
|
input >> j2; // parses the next value
|
||||||
```
|
```
|
||||||
|
|
||||||
Note this does **not** work for [JSON Lines](../features/parsing/json_lines.md) (newline-delimited JSON) input --
|
!!! warning "A number must be followed by whitespace"
|
||||||
see that page for why and for the recommended alternative.
|
|
||||||
|
A number is only terminated by the character that follows it. That character is read from the stream to detect the
|
||||||
|
end of the number, and it is **not** put back. When a value that is a number is immediately followed by the next
|
||||||
|
value, the first character of that next value is lost:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::istringstream input("1true");
|
||||||
|
json j1, j2;
|
||||||
|
input >> j1; // j1 == 1
|
||||||
|
input >> j2; // throws parse_error.101: the stream now starts at "rue"
|
||||||
|
```
|
||||||
|
|
||||||
|
Separating the values with whitespace avoids this, because the character that is eaten is then the separator:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::istringstream input("1 true");
|
||||||
|
json j1, j2;
|
||||||
|
input >> j1; // j1 == 1
|
||||||
|
input >> j2; // j2 == true
|
||||||
|
```
|
||||||
|
|
||||||
|
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
|
||||||
|
`truefalse`, `[1][2]`, `{"a":1}{"b":2}`, and `"a""b"` can be read back to back without a separator.
|
||||||
|
|
||||||
|
This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
|
||||||
|
|
||||||
|
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||||
|
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||||
|
|
||||||
!!! warning "Deprecation"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ Therefore, adding object elements can yield a reallocation in which case all ite
|
|||||||
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
|
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
|
||||||
reference after the insertion point will point to the same index, which is now a different value.
|
reference after the insertion point will point to the same index, which is now a different value.
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
[`ordered_map`](ordered_map.md) has no lookup index: every key-based object operation is a linear scan, so building or
|
||||||
|
parsing an object of `n` keys costs O(n²) rather than O(n log n). See
|
||||||
|
[`ordered_map` complexity](ordered_map.md#complexity) for the per-operation table and for measured numbers.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|||||||
@@ -56,6 +56,48 @@ std::equal_to<> // since C++14
|
|||||||
- **find**
|
- **find**
|
||||||
- **insert**
|
- **insert**
|
||||||
|
|
||||||
|
## Complexity
|
||||||
|
|
||||||
|
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every
|
||||||
|
key-based operation performs a **linear scan** over the stored elements. With `n` denoting the number of elements in the
|
||||||
|
container:
|
||||||
|
|
||||||
|
| Operation | Complexity | Note |
|
||||||
|
|----------------------------------------|----------------|----------------------------------------------------------|
|
||||||
|
| **emplace** | O(n) | scans for an existing key, then appends (amortized O(1)) |
|
||||||
|
| **operator\[\]** | O(n) | delegates to **emplace** (non-const) or **at** (const) |
|
||||||
|
| **at** | O(n) | throws `#!cpp std::out_of_range` if the key is not found |
|
||||||
|
| **find** | O(n) | |
|
||||||
|
| **count** | O(n) | the result is always 0 or 1 |
|
||||||
|
| **erase(key)** | O(n) | scan, then move the remaining elements one position down |
|
||||||
|
| **erase(pos)**, **erase(first, last)** | O(n) | moves all elements after the erased range |
|
||||||
|
| **insert(value)** | O(n) | equivalent to **emplace** |
|
||||||
|
| **insert(first, last)** | O((n + m) * m) | for `m` inserted elements |
|
||||||
|
|
||||||
|
This differs from `#!cpp std::map`, where the same operations are O(log n).
|
||||||
|
|
||||||
|
!!! warning "Quadratic cost of building large objects"
|
||||||
|
|
||||||
|
Because every insertion scans all elements inserted so far, building an object of `n` distinct keys costs
|
||||||
|
**O(n²)** in total. This applies to filling an [`ordered_json`](ordered_json.md) object key by key as well as to
|
||||||
|
parsing one, since the parser inserts each key as it is read.
|
||||||
|
|
||||||
|
The cost is negligible for the object sizes typically found in configuration files or API payloads, but it grows
|
||||||
|
steeply for machine-generated objects with many thousands of keys. Measured with `-O2 -DNDEBUG` for parsing a flat
|
||||||
|
object of `n` keys, relative to `#!cpp nlohmann::json` (which uses `#!cpp std::map`):
|
||||||
|
|
||||||
|
| `n` | `json` | `ordered_json` | factor |
|
||||||
|
|--------|--------|----------------|--------|
|
||||||
|
| 2000 | 0.7 ms | 3.6 ms | 5× |
|
||||||
|
| 4000 | 0.8 ms | 14.0 ms | 19× |
|
||||||
|
| 8000 | 1.6 ms | 67.8 ms | 43× |
|
||||||
|
| 16 000 | 3.3 ms | 181.6 ms | 54× |
|
||||||
|
|
||||||
|
If key order matters for objects of that size, consider a container with a lookup index, such as
|
||||||
|
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map)
|
||||||
|
([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)), as the object type -- see
|
||||||
|
[object order](../features/object_order.md).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# Ecosystem
|
||||||
|
|
||||||
|
The projects below build on top of `nlohmann::json` rather than merely using it - schema validators, language
|
||||||
|
bindings, format converters, and similar building blocks. The list is not exhaustive, and is curated rather than
|
||||||
|
automatically generated. If you maintain or know of a project that belongs here,
|
||||||
|
[please let me know](mailto:mail@nlohmann.me).
|
||||||
|
|
||||||
|
For products, applications, and organizations that use the library, see [Customers](../home/customers.md) instead.
|
||||||
|
|
||||||
|
## Schema validation
|
||||||
|
|
||||||
|
- [**json-schema-validator**](https://github.com/pboettch/json-schema-validator), a JSON Schema (draft 7) validator
|
||||||
|
with human-readable error messages
|
||||||
|
|
||||||
|
## Serialization and reflection
|
||||||
|
|
||||||
|
- [**nlohmann_json_reflect**](https://github.com/1261385937/nlohmann_json_reflect), a reflection extension for
|
||||||
|
(de)serializing nested containers-in-structs-in-containers
|
||||||
|
|
||||||
|
## Encodings
|
||||||
|
|
||||||
|
- [**base-encode-decode**](https://github.com/saxonnicholls/base-encode-decode), a header-only Base64/32/16/8/4/2
|
||||||
|
(and DNA/RNA) encoding library, with an adapter that serializes binary data through `nlohmann::json`
|
||||||
|
|
||||||
|
## Language bindings and interop
|
||||||
|
|
||||||
|
- [**pybind11_json**](https://github.com/pybind/pybind11_json), a bidirectional type caster between
|
||||||
|
`nlohmann::json` and Python objects for [pybind11](https://github.com/pybind/pybind11) bindings
|
||||||
|
- [**nanobind_json**](https://github.com/ianhbell/nanobind_json), the same idea for
|
||||||
|
[nanobind](https://github.com/wjakob/nanobind) bindings
|
||||||
|
- [**nlohmann_json_qt**](https://github.com/dpurgin/nlohmann_json_qt), deserialization helpers for Qt types
|
||||||
|
(`QString`, `QUrl`, `QDateTime`, `QVector`, ...) from `nlohmann::json`
|
||||||
|
- [**vulkan2json**](https://github.com/Fadis/vulkan2json), serialization and deserialization of Vulkan API structs
|
||||||
|
|
||||||
|
## Format converters
|
||||||
|
|
||||||
|
- [**tojson**](https://github.com/mircodz/tojson), a header-only converter between YAML/XML documents and
|
||||||
|
`nlohmann::json`
|
||||||
|
- [**json2xml**](https://github.com/testillano/json2xml), a header-only converter from `nlohmann::json` to XML for
|
||||||
|
simple configuration documents
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Community
|
# Community
|
||||||
|
|
||||||
|
- [Ecosystem](ecosystem.md) - third-party projects built on top of this library
|
||||||
- [Code of Conduct](code_of_conduct.md) - the rules and norms of this project
|
- [Code of Conduct](code_of_conduct.md) - the rules and norms of this project
|
||||||
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
||||||
- [Governance](governance.md) - the governance model of this project
|
- [Governance](governance.md) - the governance model of this project
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
|||||||
| Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub |
|
| Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub |
|
||||||
| Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
| Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
|
| Clang 22.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
|
||||||
| CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
| CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
| CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
Hello, world!
|
Hello, world!
|
||||||
1 2 3 4 5
|
1 2 3 4 5
|
||||||
|
|
||||||
string: "Hello, world!"
|
|
||||||
number: {"floating-point":17.23,"integer":42}
|
number: {"floating-point":17.23,"integer":42}
|
||||||
null: null
|
null: null
|
||||||
|
string: "Hello, world!"
|
||||||
boolean: true
|
boolean: true
|
||||||
array: [1,2,3,4,5]
|
array: [1,2,3,4,5]
|
||||||
[json.exception.type_error.302] type must be boolean, but is string
|
[json.exception.type_error.302] type must be boolean, but is string
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// a JSON text with an array and a number inside an object
|
||||||
|
auto text = R"({"IDs": [116, 943], "Width": 800})";
|
||||||
|
|
||||||
|
// discard the array when the parser reads its opening bracket
|
||||||
|
json j_array_start = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
|
||||||
|
{
|
||||||
|
return event != json::parse_event_t::array_start;
|
||||||
|
});
|
||||||
|
|
||||||
|
// discard the same array when the parser reads its closing bracket
|
||||||
|
json j_array_end = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
|
||||||
|
{
|
||||||
|
return event != json::parse_event_t::array_end;
|
||||||
|
});
|
||||||
|
|
||||||
|
// discard the number, but keep its key
|
||||||
|
json j_value = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
|
||||||
|
{
|
||||||
|
return !(event == json::parse_event_t::value && parsed == json(800));
|
||||||
|
});
|
||||||
|
|
||||||
|
// discard the key of the number
|
||||||
|
json j_key = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
|
||||||
|
{
|
||||||
|
return !(event == json::parse_event_t::key && parsed == json("Width"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// discard the top-level object
|
||||||
|
json j_root = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
|
||||||
|
{
|
||||||
|
return event != json::parse_event_t::object_end;
|
||||||
|
});
|
||||||
|
|
||||||
|
// in every case, the discarded value is removed together with its key
|
||||||
|
std::cout << j_array_start << '\n'
|
||||||
|
<< j_array_end << '\n'
|
||||||
|
<< j_value << '\n'
|
||||||
|
<< j_key << '\n'
|
||||||
|
<< j_root << '\n';
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{"Width":800}
|
||||||
|
{"Width":800}
|
||||||
|
{"IDs":[116,943]}
|
||||||
|
{"IDs":[116,943]}
|
||||||
|
null
|
||||||
@@ -116,9 +116,20 @@ The library uses the following mapping from JSON values types to BJData types ac
|
|||||||
```
|
```
|
||||||
|
|
||||||
Likewise, when a JSON object in the above form is serialized using
|
Likewise, when a JSON object in the above form is serialized using
|
||||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. The
|
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When
|
||||||
only exception is, that when the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two
|
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a
|
||||||
integers with one being 1, a regular 1-D optimized array is generated.
|
regular 1-D optimized array is generated instead.
|
||||||
|
|
||||||
|
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
|
||||||
|
regular JSON object. This requires all of the following:
|
||||||
|
|
||||||
|
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
||||||
|
`double`, `char`, or `byte`,
|
||||||
|
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
||||||
|
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
|
||||||
|
- `"_ArrayData_"` holds exactly that many elements, and
|
||||||
|
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||||
|
`single` and `double`, an integer otherwise).
|
||||||
|
|
||||||
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
|
||||||
array input to a BJData ND-array.
|
array input to a BJData ND-array.
|
||||||
|
|||||||
@@ -35,6 +35,19 @@ The library uses the following mapping from JSON values types to BSON types:
|
|||||||
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
|
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
|
||||||
Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings.
|
Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings.
|
||||||
|
|
||||||
|
!!! warning "BSON type 0x11 interoperability"
|
||||||
|
|
||||||
|
The BSON specification defines type `0x11` as a Timestamp. This library uses marker `0x11` when serializing
|
||||||
|
`number_unsigned` values in the range `9223372036854775808..18446744073709551615`. Other BSON implementations may
|
||||||
|
therefore interpret these values as Timestamps instead of unsigned integers.
|
||||||
|
|
||||||
|
!!! info "Binary values without a subtype"
|
||||||
|
|
||||||
|
BSON requires every binary value to have a subtype. If a binary value has no subtype, this library serializes it
|
||||||
|
with the generic subtype `0x00`. After deserialization, `has_subtype()` returns `true` and `subtype()` returns `0`.
|
||||||
|
As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object,
|
||||||
|
even though the binary data is unchanged.
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
@@ -82,8 +95,19 @@ The library maps BSON record types to JSON value types as follows:
|
|||||||
|
|
||||||
!!! note "Handling of BSON type 0x11"
|
!!! note "Handling of BSON type 0x11"
|
||||||
|
|
||||||
BSON type 0x11 is used to represent uint64 numbers. This library treats these values purely as uint64 numbers
|
This library deserializes BSON type `0x11` (Timestamp) as a `number_unsigned` value. The 64-bit value is preserved,
|
||||||
and does not parse them into date-related formats.
|
but the Timestamp type information is not.
|
||||||
|
|
||||||
|
!!! warning "Lenient BSON input handling"
|
||||||
|
|
||||||
|
The BSON reader is lenient in a few areas where the BSON specification is more restrictive:
|
||||||
|
|
||||||
|
- array element keys are not checked against the required decimal sequence (`0`, `1`, `2`, ...),
|
||||||
|
- any non-zero byte is accepted as `true` for the boolean type, and
|
||||||
|
- the payload for binary subtype `0x02` is returned as-is, including its inner length prefix.
|
||||||
|
|
||||||
|
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
||||||
|
`from_bson()`.
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ extremely small code sizes, fairly small message size, and extensibility without
|
|||||||
|
|
||||||
- [CBOR Website](http://cbor.io) - the main source on CBOR
|
- [CBOR Website](http://cbor.io) - the main source on CBOR
|
||||||
- [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR
|
- [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR
|
||||||
- [RFC 7049](https://tools.ietf.org/html/rfc7049) - the CBOR specification
|
- [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html) - the CBOR specification
|
||||||
|
|
||||||
## Serialization
|
## Serialization
|
||||||
|
|
||||||
The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification
|
The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification
|
||||||
([RFC 7049](https://www.rfc-editor.org/rfc/rfc7049.html)):
|
([RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html)):
|
||||||
|
|
||||||
| JSON value type | value/range | CBOR type | first byte |
|
| JSON value type | value/range | CBOR type | first byte |
|
||||||
|-----------------|--------------------------------------------|-----------------------------------|------------|
|
|-----------------|--------------------------------------------|-----------------------------------|------------|
|
||||||
@@ -160,14 +160,11 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
|
|
||||||
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
The mapping is **incomplete** in the sense that not all CBOR types can be converted to a JSON value. The following CBOR types are not supported and will yield parse errors:
|
||||||
|
|
||||||
- date/time (0xC0..0xC1)
|
|
||||||
- bignum (0xC2..0xC3)
|
|
||||||
- decimal fraction (0xC4)
|
|
||||||
- bigfloat (0xC5)
|
|
||||||
- expected conversions (0xD5..0xD7)
|
|
||||||
- simple values (0xE0..0xF3, 0xF8)
|
- simple values (0xE0..0xF3, 0xF8)
|
||||||
- undefined (0xF7)
|
- undefined (0xF7)
|
||||||
|
|
||||||
|
Tagged items (0xC0..0xDB) are not interpreted either; see the note on tagged items below.
|
||||||
|
|
||||||
!!! warning "Negative integer overflow"
|
!!! warning "Negative integer overflow"
|
||||||
|
|
||||||
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
CBOR negative integers (major type 1) are decoded as `-1 - n`. If the encoded magnitude `n` is too large for the
|
||||||
@@ -181,7 +178,7 @@ The library maps CBOR types to JSON value types as follows:
|
|||||||
|
|
||||||
!!! warning "Tagged items"
|
!!! warning "Tagged items"
|
||||||
|
|
||||||
Tagged items will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`.
|
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||||
|
|
||||||
??? example
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ The library uses the following mapping from JSON values types to UBJSON types ac
|
|||||||
Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the
|
Note that `use_size = true` alone may result in larger representations - the benefit of this parameter is that the
|
||||||
receiving side is immediately informed on the number of elements of the container.
|
receiving side is immediately informed on the number of elements of the container.
|
||||||
|
|
||||||
|
An array whose type marker is `Z` (null), `T` (true) or `F` (false) stores no payload at all, because the marker
|
||||||
|
already is the value. Its declared count is therefore the only thing that decides how much memory the receiving side
|
||||||
|
allocates, and a handful of bytes can describe billions of elements. `from_ubjson` rejects such an array with
|
||||||
|
[`out_of_range.408`](../../home/exceptions.md#jsonexceptionout_of_range408) when the count exceeds 1,048,576
|
||||||
|
(`1 << 20`), and `to_ubjson` writes longer arrays of these types without the annotation, so any value it produces
|
||||||
|
can be read back.
|
||||||
|
|
||||||
!!! info "Binary values"
|
!!! info "Binary values"
|
||||||
|
|
||||||
If the JSON data contains the binary type, the value stored is a list of integers, as suggested by the UBJSON
|
If the JSON data contains the binary type, the value stored is a list of integers, as suggested by the UBJSON
|
||||||
|
|||||||
@@ -47,6 +47,58 @@ json j = {{"one", 1}, {"two", 2}};
|
|||||||
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
|
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`#!cpp std::pair` and `#!cpp std::tuple` are also supported, converting positionally to and from a JSON array:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
json j = {1.0, "hello", 42};
|
||||||
|
auto t = j.get<std::tuple<double, std::string, int>>(); // {1.0, "hello", 42}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! warning "Serializing a `std::pair`/`std::tuple` whose every element is a string-keyed pair"
|
||||||
|
|
||||||
|
When *every* element of a `#!cpp std::pair` or `#!cpp std::tuple` is itself a two-element array whose first
|
||||||
|
element is a string (for example `#!cpp std::pair<std::string, int>`), serializing it produces a JSON **object**
|
||||||
|
instead of the expected array:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
using kv = std::pair<std::string, int>;
|
||||||
|
json j = std::pair<kv, kv>{{"a", 1}, {"b", 2}}; // {"a":1,"b":2}, not [["a",1],["b",2]]
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a consequence of the [brace-initializer object-detection rule](creating_values.md): the same rule that
|
||||||
|
lets `#!cpp json{{"a", 1}, {"b", 2}}` create an object also fires here. The resulting object cannot be read back
|
||||||
|
into the original type (`#!cpp get<std::pair<kv, kv>>()` throws [`type_error.302`](../home/exceptions.md#jsonexceptiontype_error302)),
|
||||||
|
and duplicate keys collapse into one, losing elements. This only affects `#!cpp std::pair`/`#!cpp std::tuple`
|
||||||
|
themselves; a `#!cpp std::vector<std::pair<std::string, int>>`, or a pair/tuple with at least one element that is
|
||||||
|
not a string-keyed pair, serializes to an array as expected. To force an array, build one explicitly from the
|
||||||
|
elements with [`array`](../api/basic_json/array.md):
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::pair<kv, kv> p{{"a", 1}, {"b", 2}};
|
||||||
|
json a = json::array({p.first, p.second}); // [["a",1],["b",2]]
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! info "Extracting references into a tuple"
|
||||||
|
|
||||||
|
A tuple type may also hold references (e.g. `#!cpp std::tuple<double&, std::string&>`) to avoid copying: `get`
|
||||||
|
then returns a tuple of references pointing directly at the elements stored inside the `basic_json` array,
|
||||||
|
rather than a tuple of copies:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
json j = {1.0, "hello"};
|
||||||
|
auto refs = j.get<std::tuple<double&, std::string&>>();
|
||||||
|
std::get<1>(refs) = "world"; // modifies j[1] in place
|
||||||
|
```
|
||||||
|
|
||||||
|
A referenced element must name the type the library actually *stores* — one of [`boolean_t`](../api/basic_json/boolean_t.md),
|
||||||
|
[`number_integer_t`](../api/basic_json/number_integer_t.md), [`number_unsigned_t`](../api/basic_json/number_unsigned_t.md),
|
||||||
|
[`number_float_t`](../api/basic_json/number_float_t.md), [`string_t`](../api/basic_json/string_t.md),
|
||||||
|
[`binary_t`](../api/basic_json/binary_t.md), [`array_t`](../api/basic_json/array_t.md), or
|
||||||
|
[`object_t`](../api/basic_json/object_t.md). There is nothing else to refer to, so a reference to any other type is a
|
||||||
|
compile error even when a conversion would exist: `#!cpp std::tuple<int&>` is rejected, because the library stores a
|
||||||
|
`#!cpp number_integer_t` (`#!cpp std::int64_t` by default) and not an `#!cpp int`. This restriction applies only to
|
||||||
|
reference elements — a plain `#!cpp std::tuple<int>` converts by value as usual.
|
||||||
|
|
||||||
## Implicit conversions
|
## Implicit conversions
|
||||||
|
|
||||||
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
|
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
|
||||||
@@ -94,17 +146,34 @@ which forces the explicit `get` form and can catch unintended conversions at com
|
|||||||
with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()`
|
with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()`
|
||||||
over `static_cast` for optional types.
|
over `static_cast` for optional types.
|
||||||
|
|
||||||
!!! warning "Converting to a fixed-size `std::array` does not check length"
|
!!! warning "Converting to a fixed-size destination does not check the array size"
|
||||||
|
|
||||||
Converting a JSON array to `#!cpp std::array<T, N>` does not check that the JSON array's size matches `N`:
|
Some destination types have a size that is fixed by their C++ type rather than by the JSON value:
|
||||||
if the JSON array is longer, the extra elements are silently dropped; if it is shorter, the remaining
|
`#!cpp std::pair<A, B>`, `#!cpp std::tuple<Ts...>`, `#!cpp std::array<T, N>`, C arrays `#!cpp T[N]`, and
|
||||||
`std::array` elements are left default-constructed. No exception is thrown in either case.
|
`#!cpp std::map`/`#!cpp std::unordered_map` with a non-string key type (which is read from an array of
|
||||||
|
two-element arrays). All of them read exactly as many elements as they need via
|
||||||
|
[`at`](../api/basic_json/at.md) and **never compare the JSON array's size to that number**. The two
|
||||||
|
mismatch directions therefore behave differently:
|
||||||
|
|
||||||
|
- The JSON array has **too many** elements: the surplus is **silently discarded**, and no exception is
|
||||||
|
thrown.
|
||||||
|
- The JSON array has **too few** elements: `at` throws
|
||||||
|
[`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index --
|
||||||
|
an out-of-range error, not a [`type_error`](../home/exceptions.md#type-errors), even though the cause
|
||||||
|
is a shape mismatch.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
json j = {1, 2, 3, 4, 5};
|
json j = {1, 2, 3, 4, 5};
|
||||||
|
|
||||||
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
|
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
|
||||||
|
auto p = j.get<std::pair<int, int>>(); // (1, 2) -- elements 3, 4, and 5 silently dropped
|
||||||
|
|
||||||
|
json k = {1};
|
||||||
|
auto q = k.get<std::pair<int, int>>(); // ❌ throws out_of_range.401
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If a size mismatch is an error in your application, check the size yourself before converting.
|
||||||
|
|
||||||
## Omitting a field when serializing `std::optional`
|
## Omitting a field when serializing `std::optional`
|
||||||
|
|
||||||
By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way
|
By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way
|
||||||
@@ -136,6 +205,20 @@ std::vector<int> numbers = {1, 2, 3};
|
|||||||
json j = numbers; // [1,2,3]
|
json j = numbers; // [1,2,3]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! info "Constructing from a C++20 range view"
|
||||||
|
|
||||||
|
A `json` array can also be constructed directly from a C++20 range view (`std::ranges::view`), such as the result
|
||||||
|
of `std::views::filter` or `std::views::transform` -- no intermediate container is needed:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
std::vector<int> nums{1, 2, 37, 42, 21};
|
||||||
|
auto filtered = nums | std::views::filter([](int i) { return i > 10; });
|
||||||
|
json j(filtered); // [37,42,21]
|
||||||
|
```
|
||||||
|
|
||||||
|
This requires [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) to be enabled and is unavailable on MinGW due
|
||||||
|
to incomplete C++20 ranges support there.
|
||||||
|
|
||||||
## Your own types
|
## Your own types
|
||||||
|
|
||||||
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
|
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
|
||||||
|
|||||||
@@ -137,6 +137,14 @@ behavior is deprecated and switched off (`0`) by default.
|
|||||||
|
|
||||||
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
|
||||||
|
|
||||||
|
## `JSON_USE_SIMDUTF`
|
||||||
|
|
||||||
|
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
|
||||||
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. This is an opt-in
|
||||||
|
external dependency and is not defined by default.
|
||||||
|
|
||||||
|
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
|
||||||
|
|
||||||
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
|
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
|
||||||
|
|
||||||
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
|
|||||||
|
|
||||||
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
|
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
|
||||||
|
|
||||||
|
The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
|
||||||
|
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
|
||||||
|
typical object sizes but becomes significant for objects with many thousands of keys; see
|
||||||
|
[`ordered_map` complexity](../api/ordered_map.md#complexity). The alternatives above keep a lookup index and do not
|
||||||
|
have this cost.
|
||||||
|
|
||||||
### Notes on parsing
|
### Notes on parsing
|
||||||
|
|
||||||
Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file.
|
Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file.
|
||||||
|
|||||||
@@ -28,6 +28,22 @@ Inputs consisting of multiple values separated by newlines are handled by the [J
|
|||||||
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
|
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
|
||||||
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
|
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
|
||||||
|
|
||||||
|
## Strictness and trailing data
|
||||||
|
|
||||||
|
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
|
||||||
|
non-whitespace data after the value is reported as a parse error. Use it when you want to guarantee that an input is
|
||||||
|
exactly one complete JSON document.
|
||||||
|
|
||||||
|
[`operator>>`](../../api/operator_gtgt.md) follows relaxed `#!cpp std::istream` semantics instead: it parses one JSON
|
||||||
|
value and leaves the stream positioned right after it, without requiring the rest of the stream to be consumed. This is
|
||||||
|
what makes it possible to read several concatenated values from the same stream, but it also means that "a valid
|
||||||
|
document followed by trailing bytes" is accepted rather than rejected. If you are validating conformance, or need to
|
||||||
|
reject any input that is not exactly one JSON document, prefer `parse`.
|
||||||
|
|
||||||
|
When using `operator>>` to read several concatenated values this way, a value that is a number must be followed by
|
||||||
|
whitespace, because `operator>>` consumes the character that terminates a number — see the
|
||||||
|
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|
||||||
The library offers two parsing models:
|
The library offers two parsing models:
|
||||||
|
|||||||
@@ -49,4 +49,5 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa
|
|||||||
with a JSON Lines input does not work, because the parser will try to parse one value after the last one.
|
with a JSON Lines input does not work, because the parser will try to parse one value after the last one.
|
||||||
|
|
||||||
This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which
|
This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which
|
||||||
`operator>>` does work -- see its [notes](../../api/operator_gtgt.md#notes) for details.
|
`operator>>` does work, provided that a value that is a number is followed by whitespace -- see its
|
||||||
|
[notes](../../api/operator_gtgt.md#notes) for details.
|
||||||
|
|||||||
@@ -8,41 +8,72 @@ the result of an internet search. If you know further customers of the library,
|
|||||||
## Space Exploration
|
## Space Exploration
|
||||||
|
|
||||||
- [**Peregrine Lunar Lander Flight 01**](https://en.wikipedia.org/wiki/Peregrine_Mission_One) - The library was used for payload management in the **Peregrine Moon Lander**, developed by **Astrobotic Technology** and launched as part of NASA's **Commercial Lunar Payload Services (CLPS)** program. After six days in orbit, the spacecraft was intentionally redirected into Earth's atmosphere, where it burned up over the Pacific Ocean on **January 18, 2024**.
|
- [**Peregrine Lunar Lander Flight 01**](https://en.wikipedia.org/wiki/Peregrine_Mission_One) - The library was used for payload management in the **Peregrine Moon Lander**, developed by **Astrobotic Technology** and launched as part of NASA's **Commercial Lunar Payload Services (CLPS)** program. After six days in orbit, the spacecraft was intentionally redirected into Earth's atmosphere, where it burned up over the Pacific Ocean on **January 18, 2024**.
|
||||||
|
- [**NASA Unsteady Pressure-Sensitive Paint Processing**](https://github.com/nasa/upsp-processing), NASA software for processing high-speed video recordings of wind tunnel tests on launch vehicle and aircraft models
|
||||||
|
- [**Terma TEMU**](https://temu.terma.com/docs/public/temu-release-notes/latest/copying/json-for-modern-cpp.html), an emulator of spacecraft on-board computers used to develop and validate flight software for European space missions
|
||||||
|
|
||||||
## Automotive
|
## Automotive
|
||||||
|
|
||||||
- [**Alexa Auto SDK**](https://github.com/alexa/alexa-auto-sdk), a software development kit enabling the integration of Alexa into automotive systems
|
- [**Alexa Auto SDK**](https://github.com/alexa/alexa-auto-sdk), a software development kit enabling the integration of Alexa into automotive systems
|
||||||
- [**Apollo**](https://github.com/ApolloAuto/apollo), a framework for building autonomous driving systems
|
- [**Apollo**](https://github.com/ApolloAuto/apollo), a framework for building autonomous driving systems
|
||||||
- [**Automotive Grade Linux (AGL)**](https://download.automotivelinux.org/AGL/release/jellyfish/latest/qemux86-64/deploy/licenses/nlohmann-json/), a collaborative open-source platform for automotive software development
|
- [**Automotive Grade Linux (AGL)**](https://download.automotivelinux.org/AGL/release/jellyfish/latest/qemux86-64/deploy/licenses/nlohmann-json/), a collaborative open-source platform for automotive software development
|
||||||
|
- [**Autoware**](https://github.com/autowarefoundation/autoware_universe), an open-source software stack for autonomous driving built on ROS 2
|
||||||
|
- [**Eclipse S-CORE**](https://github.com/eclipse-score/nlohmann_json), an open-source software platform for the software-defined vehicle backed by major automotive manufacturers and suppliers
|
||||||
- [**Genesis Motor** (infotainment)](http://webmanual.genesis.com/ccIC/AVNT/JW/KOR/English/reference010.html), a luxury automotive brand
|
- [**Genesis Motor** (infotainment)](http://webmanual.genesis.com/ccIC/AVNT/JW/KOR/English/reference010.html), a luxury automotive brand
|
||||||
- [**Hyundai** (infotainment)](https://www.hyundai.com/wsvc/ww/download.file.do?id=/content/hyundai/ww/data/opensource/data/GN7-2022/licenseCode/info), a global automotive brand
|
- [**Hyundai** (infotainment)](https://www.hyundai.com/wsvc/ww/download.file.do?id=/content/hyundai/ww/data/opensource/data/GN7-2022/licenseCode/info), a global automotive brand
|
||||||
- [**Kia** (infotainment)](http://webmanual.kia.com/PREM_GEN6/AVNT/RJPE/KOR/Korean/reference010.html), a global automotive brand
|
- [**Kia** (infotainment)](http://webmanual.kia.com/PREM_GEN6/AVNT/RJPE/KOR/Korean/reference010.html), a global automotive brand
|
||||||
- [**Mercedes-Benz Operating System (MB.OS)**](https://group.mercedes-benz.com/careers/about-us/mercedes-benz-operating-system/), a core component of the vehicle software ecosystem from Mercedes-Benz
|
- [**Mercedes-Benz Operating System (MB.OS)**](https://group.mercedes-benz.com/careers/about-us/mercedes-benz-operating-system/), a core component of the vehicle software ecosystem from Mercedes-Benz
|
||||||
|
- [**NVIDIA DRIVE OS**](https://developer.nvidia.com/docs/drive/drive-os/6.0.5/public/driveworks-nvcgf/dwx_open_source_attribution.html), the operating system and DriveWorks SDK powering NVIDIA's platform for autonomous vehicles
|
||||||
- [**Rivian** (infotainment)](https://assets.ctfassets.net/2md5qhoeajym/3cwyo4eoufk4yingUwusFt/ded2c47da620fdfc99c88c7156d2c1d8/In-Vehicle_OSS_Attribution_2024__11-24_.pdf), an electric vehicle manufacturer
|
- [**Rivian** (infotainment)](https://assets.ctfassets.net/2md5qhoeajym/3cwyo4eoufk4yingUwusFt/ded2c47da620fdfc99c88c7156d2c1d8/In-Vehicle_OSS_Attribution_2024__11-24_.pdf), an electric vehicle manufacturer
|
||||||
- [**Suzuki** (infotainment)](https://www.globalsuzuki.com/motorcycle/ipc/oss/oss_48KA_00.pdf), a global automotive and motorcycle manufacturer
|
- [**Suzuki** (infotainment)](https://www.globalsuzuki.com/motorcycle/ipc/oss/oss_48KA_00.pdf), a global automotive and motorcycle manufacturer
|
||||||
|
|
||||||
## Gaming and Entertainment
|
## Gaming and Entertainment
|
||||||
|
|
||||||
|
- [**Anno 117: Pax Romana**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a city-building strategy game set in the Roman Empire
|
||||||
- [**Assassin's Creed: Mirage**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a stealth-action game set in the Middle East, focusing on the journey of a young assassin with classic parkour and stealth mechanics
|
- [**Assassin's Creed: Mirage**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a stealth-action game set in the Middle East, focusing on the journey of a young assassin with classic parkour and stealth mechanics
|
||||||
|
- [**Battlefield 6**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a military first-person shooter known for its large-scale multiplayer battles
|
||||||
|
- [**Battlefield: REDSEC**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a free-to-play battle royale experience set in the Battlefield universe
|
||||||
|
- [**BioMenace: Remastered**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a remaster of the classic side-scrolling platform shooter
|
||||||
- [**Chasm: The Rift**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a first-person shooter blending horror and adventure, where players navigate dark realms and battle monsters
|
- [**Chasm: The Rift**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a first-person shooter blending horror and adventure, where players navigate dark realms and battle monsters
|
||||||
- [**College Football 25**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a college football simulation game featuring gameplay that mimics real-life college teams and competitions
|
- [**College Football 25**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a college football simulation game featuring gameplay that mimics real-life college teams and competitions
|
||||||
|
- [**College Football 26**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a college football simulation game featuring licensed teams and stadiums
|
||||||
|
- [**College Football 27**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), the latest installment of the college football simulation series
|
||||||
- [**Concepts**](https://concepts.app/en/licenses), a digital sketching app designed for creative professionals, offering flexible drawing tools for illustration, design, and brainstorming
|
- [**Concepts**](https://concepts.app/en/licenses), a digital sketching app designed for creative professionals, offering flexible drawing tools for illustration, design, and brainstorming
|
||||||
- [**Depthkit**](https://www.depthkit.tv/third-party-licenses), a tool for creating and capturing volumetric video, enabling immersive 3D experiences and interactive content
|
- [**Depthkit**](https://www.depthkit.tv/third-party-licenses), a tool for creating and capturing volumetric video, enabling immersive 3D experiences and interactive content
|
||||||
|
- [**Dune: Awakening**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an open-world survival MMO set on the desert planet Arrakis
|
||||||
|
- [**EA Sports FC 25**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an association football simulation with club, career, and online modes
|
||||||
|
- [**EA Sports FC 26**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), the latest installment of the association football simulation series
|
||||||
|
- [**EA Sports UFC 6**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a mixed martial arts fighting simulation
|
||||||
|
- [**FiveM**](https://github.com/citizenfx/fivem), a modification framework for Grand Theft Auto V that powers custom multiplayer servers
|
||||||
|
- [**FLUX:: Immersive**](https://doc.flux.audio/syrah/Credits.html), a suite of professional audio processing and immersive mixing plugins used in music and post-production
|
||||||
- [**IMG.LY**](https://img.ly/acknowledgements), a platform offering creative tools and SDKs for integrating advanced image and video editing in applications
|
- [**IMG.LY**](https://img.ly/acknowledgements), a platform offering creative tools and SDKs for integrating advanced image and video editing in applications
|
||||||
|
- [**immersivetech**](https://immersitech.io/open-source-third-party-software/), a technology company focused on immersive experiences, providing tools and solutions for virtual and augmented reality applications
|
||||||
|
- [**Kodi**](https://github.com/xbmc/xbmc/blob/master/xbmc/utils/JSONVariantWriter.cpp), a home theater and media center application
|
||||||
- [**LOOT**](https://loot.readthedocs.io/_/downloads/en/0.13.0/pdf/), a tool for optimizing the load order of game plugins, commonly used in The Elder Scrolls and Fallout series
|
- [**LOOT**](https://loot.readthedocs.io/_/downloads/en/0.13.0/pdf/), a tool for optimizing the load order of game plugins, commonly used in The Elder Scrolls and Fallout series
|
||||||
|
- [**LunaTranslator**](https://github.com/HIllya51/LunaTranslator/blob/main/src/NativeImpl/LunaSubprocess/aspatch.cpp), a real-time translation tool for visual novels
|
||||||
|
- [**MaaAssistantArknights**](https://github.com/MaaAssistantArknights/MaaAssistantArknights/blob/dev-v2/src/MaaCore/Vision/Roguelike/BlackFlow/BlackFlowMapAnalyzer.cpp), an automation assistant for the mobile game Arknights
|
||||||
- [**Madden NFL 25**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a sports simulation game capturing the excitement of American football with realistic gameplay and team management features
|
- [**Madden NFL 25**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a sports simulation game capturing the excitement of American football with realistic gameplay and team management features
|
||||||
|
- [**Madden NFL 26**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an American football simulation with franchise and team management modes
|
||||||
|
- [**Madden NFL 27**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), the latest installment of the American football simulation series
|
||||||
- [**Marne**](https://marne.io/licenses), an unofficial private server platform for hosting custom Battlefield 1 game experiences
|
- [**Marne**](https://marne.io/licenses), an unofficial private server platform for hosting custom Battlefield 1 game experiences
|
||||||
- [**Minecraft**](https://www.minecraft.net/zh-hant/attribution), a popular sandbox video game
|
- [**Minecraft**](https://www.minecraft.net/zh-hant/attribution), a popular sandbox video game
|
||||||
|
- [**Mumble**](https://github.com/mumble-voip/mumble), a low-latency, open-source voice chat application widely used by gaming communities
|
||||||
- [**NHL 22**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a hockey simulation game offering realistic gameplay, team management, and various modes to enhance the hockey experience
|
- [**NHL 22**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a hockey simulation game offering realistic gameplay, team management, and various modes to enhance the hockey experience
|
||||||
|
- [**OBS Studio**](https://github.com/obsproject/obs-studio), a free and open-source suite for video recording and live streaming
|
||||||
|
- [**OpenRCT2**](https://github.com/OpenRCT2/OpenRCT2/blob/develop/src/openrct2/core/JsonFwd.hpp), an open source re-implementation of RollerCoaster Tycoon 2
|
||||||
- [**Pixelpart**](https://pixelpart.net/documentation/book/third-party.html), a 2D animation and video compositing software that allows users to create animated graphics and visual effects with a focus on simplicity and ease of use
|
- [**Pixelpart**](https://pixelpart.net/documentation/book/third-party.html), a 2D animation and video compositing software that allows users to create animated graphics and visual effects with a focus on simplicity and ease of use
|
||||||
- [**Razer Cortex**](https://mysupport.razer.com/app/answers/detail/a_id/14146/~/open-source-software-for-razer-software), a gaming performance optimizer and system booster designed to enhance the gaming experience
|
- [**Razer Cortex**](https://mysupport.razer.com/app/answers/detail/a_id/14146/~/open-source-software-for-razer-software), a gaming performance optimizer and system booster designed to enhance the gaming experience
|
||||||
- [**Red Dead Redemption II**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an open-world action-adventure game following an outlaw's story in the late 1800s, emphasizing deep storytelling and immersive gameplay
|
- [**Red Dead Redemption II**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an open-world action-adventure game following an outlaw's story in the late 1800s, emphasizing deep storytelling and immersive gameplay
|
||||||
|
- [**RetroArch**](https://github.com/libretro/RetroArch), a frontend for emulators, game engines, and media players built on the libretro API
|
||||||
|
- [**shadPS4**](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/user_manager.h), a PlayStation 4 emulator for Windows, Linux and macOS
|
||||||
|
- [**skate.**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a free-to-play skateboarding game set in an open world
|
||||||
- [**Snapchat**](https://www.snap.com/terms/license-android), a multimedia messaging and augmented reality app for communication and entertainment
|
- [**Snapchat**](https://www.snap.com/terms/license-android), a multimedia messaging and augmented reality app for communication and entertainment
|
||||||
|
- [**Steel Century Groove**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), an action game released in 2026
|
||||||
|
- [**Sunshine**](https://github.com/LizardByte/Sunshine/blob/master/src/confighttp.h), a self-hosted game streaming host compatible with Moonlight clients
|
||||||
- [**Tactics Ogre: Reborn**](https://www.square-enix-games.com/en_US/documents/tactics-ogre-reborn-pc-installer-software-and-associated-plug-ins-disclosure), a tactical role-playing game featuring strategic battles and deep storytelling elements
|
- [**Tactics Ogre: Reborn**](https://www.square-enix-games.com/en_US/documents/tactics-ogre-reborn-pc-installer-software-and-associated-plug-ins-disclosure), a tactical role-playing game featuring strategic battles and deep storytelling elements
|
||||||
- [**Throne and Liberty**](https://www.amazon.com/gp/help/customer/display.html?nodeId=T7fLNw5oAevCMtJFPj&pop-up=1), an MMORPG that offers an expansive fantasy world with dynamic gameplay and immersive storytelling
|
- [**Throne and Liberty**](https://www.amazon.com/gp/help/customer/display.html?nodeId=T7fLNw5oAevCMtJFPj&pop-up=1), an MMORPG that offers an expansive fantasy world with dynamic gameplay and immersive storytelling
|
||||||
- [**Unity Vivox**](https://docs.unity3d.com/Packages/com.unity.services.vivox@15.1/license/Third%20Party%20Notices.html), a communication service that enables voice and text chat functionality in multiplayer games developed with Unity
|
- [**Unity Vivox**](https://docs.unity3d.com/Packages/com.unity.services.vivox@15.1/license/Third%20Party%20Notices.html), a communication service that enables voice and text chat functionality in multiplayer games developed with Unity
|
||||||
|
- [**xemu**](https://github.com/xemu-project/xemu), an emulator of the original Xbox console
|
||||||
- [**Zool: Redimensioned**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a modern reimagining of the classic platformer featuring fast-paced gameplay and vibrant environments
|
- [**Zool: Redimensioned**](https://www.mobygames.com/person/1195889/niels-lohmann/credits/), a modern reimagining of the classic platformer featuring fast-paced gameplay and vibrant environments
|
||||||
- [**immersivetech**](https://immersitech.io/open-source-third-party-software/), a technology company focused on immersive experiences, providing tools and solutions for virtual and augmented reality applications
|
|
||||||
|
|
||||||
## Consumer Electronics
|
## Consumer Electronics
|
||||||
|
|
||||||
@@ -50,109 +81,195 @@ the result of an internet search. If you know further customers of the library,
|
|||||||
- [**Canon CanoScan LIDE**](https://carolburo.com/wp-content/uploads/2024/06/LiDE400_OnlineManual_Win_FR_V02.pdf), a series of flatbed scanners offering high-resolution image scanning for home and office use
|
- [**Canon CanoScan LIDE**](https://carolburo.com/wp-content/uploads/2024/06/LiDE400_OnlineManual_Win_FR_V02.pdf), a series of flatbed scanners offering high-resolution image scanning for home and office use
|
||||||
- [**Canon PIXMA Printers**](https://www.mediaexpert.pl/products/files/73/7338196/Instrukcja-obslugi-CANON-Pixma-TS7450i.pdf), a line of all-in-one inkjet printers known for high-quality printing and wireless connectivity
|
- [**Canon PIXMA Printers**](https://www.mediaexpert.pl/products/files/73/7338196/Instrukcja-obslugi-CANON-Pixma-TS7450i.pdf), a line of all-in-one inkjet printers known for high-quality printing and wireless connectivity
|
||||||
- [**Cisco Webex Desk Camera**](https://www.cisco.com/c/dam/en_us/about/doing_business/open_source/docs/CiscoWebexDeskCamera-23-1622100417.pdf), a video camera designed for professional-quality video conferencing and remote collaboration
|
- [**Cisco Webex Desk Camera**](https://www.cisco.com/c/dam/en_us/about/doing_business/open_source/docs/CiscoWebexDeskCamera-23-1622100417.pdf), a video camera designed for professional-quality video conferencing and remote collaboration
|
||||||
|
- [**DJI Edge SDK**](https://github.com/dji-sdk/Edge-SDK-V2-Demo), the reference applications for DJI's Edge SDK, used to build edge computing services on DJI drone docks
|
||||||
|
- [**Elgato Stream Deck**](https://github.com/elgatosf/streamdeck-obs-plugin2), a family of programmable control surfaces for content creators and their plugin ecosystem
|
||||||
|
- [**Instagrid**](https://instagrid.co/intellectual-property/foss), a manufacturer of portable, high-performance battery systems for professional mobile power supply
|
||||||
|
- [**iRobot**](https://iot-content.irobot.com/iw/sfsites/c/cms/delivery/media/MCKRLTPDJSSJBNJKDA5SG5UVVIIQ), a manufacturer of autonomous home robots including the Roomba vacuum cleaner range
|
||||||
|
- [**Logitech Logi Bolt**](https://opensource.logitech.com/wiki/Logi_BoltApp/), the management application for Logitech's secure wireless connectivity technology
|
||||||
|
- [**Novitus**](https://novitus.pl/licencjepensource), a manufacturer of fiscal cash registers and point-of-sale devices
|
||||||
- [**Philips Hue Personal Wireless Lighting**](http://2ak5ape.257.cz/), a smart lighting system for customizable and wireless home illumination
|
- [**Philips Hue Personal Wireless Lighting**](http://2ak5ape.257.cz/), a smart lighting system for customizable and wireless home illumination
|
||||||
- [**Ray-Ban Meta Smart glasses**](https://www.meta.com/de/en/legal/smart-glasses/third-party-notices-android/03/), a pair of smart glasses designed for capturing photos and videos with integrated connectivity and social features
|
- [**Ray-Ban Meta Smart glasses**](https://www.meta.com/de/en/legal/smart-glasses/third-party-notices-android/03/), a pair of smart glasses designed for capturing photos and videos with integrated connectivity and social features
|
||||||
- [**Razer Synapse**](https://mysupport.razer.com/app/answers/detail/a_id/14146/~/open-source-software-for-razer-software), a unified configuration software enabling hardware customization for Razer devices
|
- [**Razer Synapse**](https://mysupport.razer.com/app/answers/detail/a_id/14146/~/open-source-software-for-razer-software), a unified configuration software enabling hardware customization for Razer devices
|
||||||
|
- [**Sharp Professional Displays**](https://jp.sharp/restricted/business/lcd-display/cms/images/source_pnla862/PN-LA652_752_862_LicenseInformation.pdf), a range of large-format interactive displays for business and education
|
||||||
- [**Siemens SINEMA Remote Connect**](https://cache.industry.siemens.com/dl/files/790/109793790/att_1054961/v2/OSS_SINEMA-RC_86.pdf), a remote connectivity solution for monitoring and managing industrial networks and devices securely
|
- [**Siemens SINEMA Remote Connect**](https://cache.industry.siemens.com/dl/files/790/109793790/att_1054961/v2/OSS_SINEMA-RC_86.pdf), a remote connectivity solution for monitoring and managing industrial networks and devices securely
|
||||||
|
- [**Skydio**](https://pages.skydio.com/rs/784-TUF-591/images/Open%20Source%20Software%20Notice%20v0.2.html), a manufacturer of autonomous drones for inspection, public safety, and defense applications
|
||||||
- [**Sony PlayStation 4**](https://doc.dl.playstation.net/doc/ps4-oss/index.html), a gaming console developed by Sony that offers a wide range of games and multimedia entertainment features
|
- [**Sony PlayStation 4**](https://doc.dl.playstation.net/doc/ps4-oss/index.html), a gaming console developed by Sony that offers a wide range of games and multimedia entertainment features
|
||||||
|
- [**Sony Spatial Reality Display**](https://www.sony.co.jp/en/Products/Developer-Spatial-Reality-display/download/dcc-tools/blender-plugin/SpatiaRealityDisplayPluginforPreviewBL_Manual.pdf), a glasses-free stereoscopic 3D display and its plugins for Blender, 3ds Max, and ZBrush
|
||||||
- [**Sony Virtual Webcam Driver for Remote Camera**](https://helpguide.sony.net/rc/vwd/v1/zh-cn/print.pdf), a software driver that enables the use of Sony cameras as virtual webcams for video conferencing and streaming
|
- [**Sony Virtual Webcam Driver for Remote Camera**](https://helpguide.sony.net/rc/vwd/v1/zh-cn/print.pdf), a software driver that enables the use of Sony cameras as virtual webcams for video conferencing and streaming
|
||||||
|
- [**Yamaha Clavinova**](https://usa.yamaha.com/files/download/other_assets/1/2298171/CLP-800_oss_license.pdf), a series of digital pianos combining acoustic piano feel with digital sound technology
|
||||||
|
|
||||||
## Operating Systems
|
## Operating Systems and Platforms
|
||||||
|
|
||||||
- [**Apple iOS and macOS**](https://www.apple.com/macos), a family of operating systems developed by Apple, including iOS for mobile devices and macOS for desktop computers
|
- [**Apple iOS and macOS**](https://www.apple.com/macos), a family of operating systems developed by Apple, including iOS for mobile devices and macOS for desktop computers
|
||||||
|
- [**Chromium**](https://chromium.googlesource.com/chromium/src/+/main/third_party/nlohmann_json/), the open-source browser project that Google Chrome, Microsoft Edge, and many other browsers are built on, where the library is used as data container for on-device model execution
|
||||||
- [**Google Fuchsia**](https://fuchsia.googlesource.com/third_party/json/), an open-source operating system developed by Google, designed to be secure, updatable, and adaptable across various devices
|
- [**Google Fuchsia**](https://fuchsia.googlesource.com/third_party/json/), an open-source operating system developed by Google, designed to be secure, updatable, and adaptable across various devices
|
||||||
|
- [**LG webOS**](https://github.com/webosose/com.webos.service.camera), a Linux-based operating system used in LG smart TVs, signage, and embedded devices
|
||||||
|
- [**Microsoft Azure Linux**](https://github.com/microsoft/azurelinux), a Linux distribution developed by Microsoft for Azure infrastructure and edge workloads
|
||||||
|
- [**OpenHarmony**](https://github.com/openharmony/third_party_json), an open-source operating system for smart devices and the foundation of HarmonyOS
|
||||||
- [**SerenityOS**](https://github.com/SerenityOS/serenity), an open-source operating system that aims to provide a simple and beautiful user experience with a focus on simplicity and elegance
|
- [**SerenityOS**](https://github.com/SerenityOS/serenity), an open-source operating system that aims to provide a simple and beautiful user experience with a focus on simplicity and elegance
|
||||||
|
- [**Windows Subsystem for Linux**](https://github.com/microsoft/WSL), a compatibility layer that runs Linux environments natively on Windows
|
||||||
- [**Yocto**](http://ftp.emacinc.com/openembedded-sw/kirkstone-icop-5.15-kirkstone-6.0/archive-2024-10/pn8m-090t-ppc/licenses/nlohmann-json/), a Linux-based build system for creating custom operating systems and software distributions, tailored for embedded devices and IoT applications
|
- [**Yocto**](http://ftp.emacinc.com/openembedded-sw/kirkstone-icop-5.15-kirkstone-6.0/archive-2024-10/pn8m-090t-ppc/licenses/nlohmann-json/), a Linux-based build system for creating custom operating systems and software distributions, tailored for embedded devices and IoT applications
|
||||||
|
|
||||||
## Development Tools and IDEs
|
## Development Tools and IDEs
|
||||||
|
|
||||||
- [**Accentize SpectralBalance**](https://www.accentize.com/products/SpectralBalanceManual.pdf), an adaptive speech analysis tool designed to enhance audio quality by optimizing frequency balance in recordings
|
- [**Accentize SpectralBalance**](https://www.accentize.com/products/SpectralBalanceManual.pdf), an adaptive speech analysis tool designed to enhance audio quality by optimizing frequency balance in recordings
|
||||||
|
- [**Airbus Ghidralligator**](https://www.cyber.airbus.com/en/newsroom/stories/2025-06-ghidralligator), a Ghidra-based emulator from Airbus CyberSecurity used to fuzz and analyse embedded firmware
|
||||||
|
- [**Apache brpc**](https://github.com/apache/brpc/blob/master/src/butil/iobuf.h), an industrial-grade remote procedure call framework for C++
|
||||||
- [**Arm Compiler for Linux**](https://documentation-service.arm.com/static/66558e9d876c8d213b7843e4), a software development toolchain for compiling and optimizing applications on Arm-based Linux systems
|
- [**Arm Compiler for Linux**](https://documentation-service.arm.com/static/66558e9d876c8d213b7843e4), a software development toolchain for compiling and optimizing applications on Arm-based Linux systems
|
||||||
- [**BBEdit**](https://s3.amazonaws.com/BBSW-download/BBEdit_15.1.2_User_Manual.pdf), a professional text and code editor for macOS
|
- [**BBEdit**](https://s3.amazonaws.com/BBSW-download/BBEdit_15.1.2_User_Manual.pdf), a professional text and code editor for macOS
|
||||||
- [**CoderPad**](https://coderpad.io), a collaborative coding platform that enables real-time code interviews and assessments for developers; the library is included in every CoderPad instance and can be accessed with a simple `#include "json.hpp"`
|
- [**CoderPad**](https://coderpad.io), a collaborative coding platform that enables real-time code interviews and assessments for developers; the library is included in every CoderPad instance and can be accessed with a simple `#include "json.hpp"`
|
||||||
|
- [**Codon**](https://github.com/exaloop/codon/blob/develop/jupyter/jupyter.h), an ahead-of-time compiler for a Python-like language
|
||||||
- [**Compiler Explorer**](https://godbolt.org), a web-based tool that allows users to write, compile, and visualize the assembly output of code in various programming languages; the library is readily available and accessible with the directive `#include <nlohmann/json.hpp>`.
|
- [**Compiler Explorer**](https://godbolt.org), a web-based tool that allows users to write, compile, and visualize the assembly output of code in various programming languages; the library is readily available and accessible with the directive `#include <nlohmann/json.hpp>`.
|
||||||
- [**GitHub CodeQL**](https://github.com/github/codeql), a code analysis tool used for identifying security vulnerabilities and bugs in software through semantic queries
|
- [**Flutter**](https://github.com/flutter/flutter/blob/master/engine/src/flutter/impeller/compiler/reflector.cc), a UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase
|
||||||
|
- [**Fraunhofer VVenC**](https://github.com/fraunhoferhhi/vvenc), a fast and efficient encoder for the Versatile Video Coding (H.266/VVC) standard
|
||||||
|
- [**GitHub CodeQL**](https://github.com/github/codeql/blob/main/shared/cpp/Diagnostics.h), a code analysis tool used for identifying security vulnerabilities and bugs in software through semantic queries
|
||||||
|
- [**GoPro ngfx**](https://github.com/gopro/ngfx), a low-level graphics abstraction and profiling framework developed by GoPro
|
||||||
|
- [**gRPC**](https://github.com/grpc/grpc/blob/master/tools/artifact_gen/utils.h), a high-performance universal remote procedure call framework
|
||||||
- [**Hex-Rays**](https://docs.hex-rays.com/user-guide/user-interface/licenses), a reverse engineering toolset for analyzing and decompiling binaries, primarily used for security research and vulnerability analysis
|
- [**Hex-Rays**](https://docs.hex-rays.com/user-guide/user-interface/licenses), a reverse engineering toolset for analyzing and decompiling binaries, primarily used for security research and vulnerability analysis
|
||||||
- [**ImHex**](https://github.com/WerWolv/ImHex), a hex editor designed for reverse engineering, providing advanced features for data analysis and manipulation
|
- [**ImHex**](https://github.com/WerWolv/ImHex), a hex editor designed for reverse engineering, providing advanced features for data analysis and manipulation
|
||||||
|
- [**Intel GITS**](https://github.com/intel/gits), a tool for capturing and replaying graphics API calls for debugging and performance analysis
|
||||||
- [**Intel GPA Framework**](https://intel.github.io/gpasdk-doc/src/licenses.html), a suite of cross-platform tools for capturing, analyzing, and optimizing graphics applications across different APIs
|
- [**Intel GPA Framework**](https://intel.github.io/gpasdk-doc/src/licenses.html), a suite of cross-platform tools for capturing, analyzing, and optimizing graphics applications across different APIs
|
||||||
- [**Intopix**](https://www.intopix.com/software-licensing), a provider of advanced image processing and compression solutions used in software development and AV workflows
|
- [**Intopix**](https://www.intopix.com/software-licensing), a provider of advanced image processing and compression solutions used in software development and AV workflows
|
||||||
- [**Java SE**](https://www.oracle.com/a/tech/docs/jdk8-lium.pdf), the core Java platform that provides the libraries and runtime needed to build and run general-purpose Java applications
|
- [**Java SE**](https://www.oracle.com/a/tech/docs/jdk8-lium.pdf), the core Java platform that provides the libraries and runtime needed to build and run general-purpose Java applications
|
||||||
- [**MKVToolNix**](https://mkvtoolnix.download/doc/README.md), a set of tools for creating, editing, and inspecting MKV (Matroska) multimedia container files
|
|
||||||
- [**Meta Yoga**](https://github.com/facebook/yoga), a layout engine that facilitates flexible and efficient user interface design across multiple platforms
|
- [**Meta Yoga**](https://github.com/facebook/yoga), a layout engine that facilitates flexible and efficient user interface design across multiple platforms
|
||||||
- [**NVIDIA Nsight Compute**](https://docs.nvidia.com/nsight-compute/2022.2/pdf/CopyrightAndLicenses.pdf), a performance analysis tool for CUDA applications that provides detailed insights into GPU performance metrics
|
- [**MKVToolNix**](https://mkvtoolnix.download/doc/README.md), a set of tools for creating, editing, and inspecting MKV (Matroska) multimedia container files
|
||||||
|
- [**MRTech IFF SDK**](https://mr-technologies.com/pub/iff-sdk-manual-2-0-1/iff-sdk-manual-2-0-1.pdf), an image processing SDK for machine vision applications with GPU-accelerated pipelines
|
||||||
|
- [**Nix**](https://github.com/NixOS/nix/blob/master/src/nix/build.cc), a purely functional package manager
|
||||||
- [**Notepad++**](https://github.com/notepad-plus-plus/notepad-plus-plus), a free source code editor that supports various programming languages
|
- [**Notepad++**](https://github.com/notepad-plus-plus/notepad-plus-plus), a free source code editor that supports various programming languages
|
||||||
|
- [**NVIDIA Nsight Compute**](https://docs.nvidia.com/nsight-compute/2022.2/pdf/CopyrightAndLicenses.pdf), a performance analysis tool for CUDA applications that provides detailed insights into GPU performance metrics
|
||||||
|
- [**openFrameworks**](https://github.com/openframeworks/openFrameworks/blob/master/libs/openFrameworks/utils/ofJson.h), a community-developed C++ toolkit for creative coding
|
||||||
- [**OpenRGB**](https://gitlab.com/CalcProgrammer1/OpenRGB), an open source RGB lighting control that doesn't depend on manufacturer software
|
- [**OpenRGB**](https://gitlab.com/CalcProgrammer1/OpenRGB), an open source RGB lighting control that doesn't depend on manufacturer software
|
||||||
- [**OpenTelemetry C++**](https://github.com/open-telemetry/opentelemetry-cpp), a library for collecting and exporting observability data in C++, enabling developers to implement distributed tracing and metrics in their application
|
- [**OpenTelemetry C++**](https://github.com/open-telemetry/opentelemetry-cpp), a library for collecting and exporting observability data in C++, enabling developers to implement distributed tracing and metrics in their application
|
||||||
|
- [**Oracle GraalVM**](https://docs.oracle.com/en/graalvm/jdk/21/docs/licensing-information/), a high-performance JDK distribution with ahead-of-time compilation and polyglot runtime support
|
||||||
|
- [**Philips amp-cucumber-cpp-runner**](https://github.com/philips-software/amp-cucumber-cpp-runner), a behaviour-driven development test runner for embedded C++ software developed at Philips
|
||||||
- [**Qt Creator**](https://doc.qt.io/qtcreator/qtcreator-attribution-json-nlohmann.html), an IDE for developing applications using the Qt application framework
|
- [**Qt Creator**](https://doc.qt.io/qtcreator/qtcreator-attribution-json-nlohmann.html), an IDE for developing applications using the Qt application framework
|
||||||
|
- [**Qt for MCUs**](https://doc.qt.io/QtForMCUs/quickultralite-attribution-nlohmann-json.html), a graphics framework for building fluid user interfaces on microcontrollers
|
||||||
|
- [**React Native**](https://github.com/react/react-native/blob/main/packages/react-native/ReactCxxPlatform/react/devsupport/PackagerConnection.cpp), a framework for building native mobile applications using React
|
||||||
- [**Scanbot SDK**](https://docs.scanbot.io/barcode-scanner-sdk/web/third-party-libraries/), a software development kit (SDK) that provides tools for integrating advanced document scanning and barcode scanning capabilities into applications
|
- [**Scanbot SDK**](https://docs.scanbot.io/barcode-scanner-sdk/web/third-party-libraries/), a software development kit (SDK) that provides tools for integrating advanced document scanning and barcode scanning capabilities into applications
|
||||||
|
- [**STMicroelectronics TouchGFX**](https://www.st.com/resource/en/additional_license_terms/additional-license-terms-x-cube-touchgfx.html), a graphical user interface framework shipped with STM32 microcontrollers for building embedded HMIs
|
||||||
|
- [**swagger-codegen**](https://github.com/swagger-api/swagger-codegen/blob/master/samples/server/petstore/pistache-server/model/Pet.h), a template-driven engine that generates API clients and server stubs from an OpenAPI specification
|
||||||
|
- [**Swoole**](https://github.com/swoole/swoole-src/blob/master/ext-src/swoole_admin_server.cc), a coroutine-based concurrency engine for PHP
|
||||||
|
- [**Tracy Profiler**](https://github.com/wolfpld/tracy/blob/master/profiler/src/profiler/TracyLlm.hpp), a real-time frame profiler for games and other applications
|
||||||
|
- [**WasmEdge**](https://github.com/WasmEdge/WasmEdge/blob/master/plugins/wasi_nn/GGML/tts/tts_core.cpp), a lightweight WebAssembly runtime for edge and cloud workloads
|
||||||
|
- [**x64dbg**](https://github.com/x64dbg/x64dbg/blob/development/src/cross/remote_table/TableRpcData.h), an open source user mode debugger for Windows, aimed at reverse engineering and malware analysis
|
||||||
|
|
||||||
## Machine Learning and AI
|
## Machine Learning and AI
|
||||||
|
|
||||||
|
- [**Alibaba MNN**](https://github.com/alibaba/MNN), a lightweight deep learning inference engine for mobile and embedded devices
|
||||||
|
- [**AMD Gaia**](https://github.com/amd/gaia), an open-source framework for running generative AI applications locally on AMD hardware
|
||||||
|
- [**AMD Vitis AI (VAIP)**](https://github.com/amd/vaip), the execution provider stack that runs AI models on AMD Ryzen AI and adaptive computing devices
|
||||||
- [**Apple Core ML Tools**](https://github.com/apple/coremltools), a set of tools for converting and configuring machine learning models for deployment in Apple's Core ML framework
|
- [**Apple Core ML Tools**](https://github.com/apple/coremltools), a set of tools for converting and configuring machine learning models for deployment in Apple's Core ML framework
|
||||||
- [**Avular Mobile Robotics**](https://www.avular.com/licenses/nlohmann-json-3.9.1.txt), a platform for developing and deploying mobile robotics solutions
|
- [**Avular Mobile Robotics**](https://www.avular.com/licenses/nlohmann-json-3.9.1.txt), a platform for developing and deploying mobile robotics solutions
|
||||||
|
- [**FunASR**](https://github.com/modelscope/FunASR/blob/main/runtime/http/bin/asr_sessions.h), a speech recognition toolkit for training and deploying end-to-end models
|
||||||
- [**Google gemma.cpp**](https://github.com/google/gemma.cpp), a lightweight C++ inference engine designed for running AI models from the Gemma family
|
- [**Google gemma.cpp**](https://github.com/google/gemma.cpp), a lightweight C++ inference engine designed for running AI models from the Gemma family
|
||||||
|
- [**Google Magenta The Infinite Crate**](https://github.com/magenta/the-infinite-crate), an open-source generative AI plugin for digital audio workstations from Google's Magenta research team
|
||||||
|
- [**GPT4All**](https://github.com/nomic-ai/gpt4all/blob/main/gpt4all-chat/src/tool.h), a desktop application for running local large language models on consumer hardware
|
||||||
|
- [**Huawei MindSpore**](https://github.com/mindspore-ai/mindspore/blob/master/Third_Party_Open_Source_Software_Notice), a deep learning framework for training and inference across device, edge, and cloud
|
||||||
|
- [**KTransformers**](https://github.com/kvcache-ai/ktransformers/blob/main/archive/csrc/balance_serve/sched/model_config.h), a framework for heterogeneous large language model inference
|
||||||
- [**llama.cpp**](https://github.com/ggerganov/llama.cpp), a C++ library designed for efficient inference of large language models (LLMs), enabling streamlined integration into applications
|
- [**llama.cpp**](https://github.com/ggerganov/llama.cpp), a C++ library designed for efficient inference of large language models (LLMs), enabling streamlined integration into applications
|
||||||
|
- [**LocalAI**](https://github.com/mudler/LocalAI/blob/master/backend/cpp/ds4/dsml_renderer.cpp), a self-hosted inference engine that exposes local models through an OpenAI-compatible API
|
||||||
- [**MLX**](https://github.com/ml-explore/mlx), an array framework for machine learning on Apple Silicon
|
- [**MLX**](https://github.com/ml-explore/mlx), an array framework for machine learning on Apple Silicon
|
||||||
- [**Mozilla llamafile**](https://github.com/Mozilla-Ocho/llamafile), a tool designed for distributing and executing large language models (LLMs) efficiently using a single file format
|
- [**Mozilla llamafile**](https://github.com/Mozilla-Ocho/llamafile), a tool designed for distributing and executing large language models (LLMs) efficiently using a single file format
|
||||||
- [**NVIDIA ACE**](https://docs.nvidia.com/ace/latest/index.html), a suite of real-time AI solutions designed for the development of interactive avatars and digital human applications, enabling scalable and sophisticated user interactions
|
- [**NVIDIA ACE**](https://docs.nvidia.com/ace/latest/index.html), a suite of real-time AI solutions designed for the development of interactive avatars and digital human applications, enabling scalable and sophisticated user interactions
|
||||||
|
- [**NVIDIA Instant NGP**](https://github.com/NVlabs/instant-ngp/blob/master/src/nerf_loader.cu), an implementation of instant neural graphics primitives for rapid scene reconstruction
|
||||||
|
- [**NVIDIA TensorRT**](https://github.com/NVIDIA/TensorRT), an SDK for high-performance deep learning inference, including its TensorRT-LLM extension for large language models
|
||||||
|
- [**NVIDIA TensorRT-LLM**](https://github.com/NVIDIA/TensorRT-LLM/blob/main/cpp/tensorrt_llm/common/safetensors.cpp), a toolkit for optimizing and serving large language model inference on GPUs
|
||||||
|
- [**ONNX Runtime**](https://github.com/microsoft/onnxruntime), a cross-platform inference and training accelerator for machine learning models
|
||||||
|
- [**OpenVINO**](https://github.com/openvinotoolkit/openvino), Intel's toolkit for optimizing and deploying deep learning inference across CPUs, GPUs, and NPUs
|
||||||
|
- [**PaddleOCR**](https://github.com/PaddlePaddle/PaddleOCR/blob/main/deploy/cpp_infer/src/modules/text_detection/result.cc), an optical character recognition toolkit that turns documents and images into structured data
|
||||||
|
- [**PaddlePaddle**](https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/ap/src/axpr/anf_expr.cc), a deep learning framework for distributed training and inference
|
||||||
- [**Peer**](https://support.peer.inc/hc/en-us/articles/17261335054235-Licenses), a platform offering personalized AI assistants for interactive learning and creative collaboration
|
- [**Peer**](https://support.peer.inc/hc/en-us/articles/17261335054235-Licenses), a platform offering personalized AI assistants for interactive learning and creative collaboration
|
||||||
|
- [**PyTorch**](https://github.com/pytorch/pytorch), a machine learning framework for building and training neural networks, widely used in research and production
|
||||||
|
- [**Qualcomm AI Engine Direct**](https://github.com/qualcomm/qai-appbuilder), a toolchain for building and running generative AI applications on Snapdragon devices
|
||||||
|
- [**sherpa-onnx**](https://github.com/k2-fsa/sherpa-onnx/blob/master/sherpa-onnx/csrc/sentence-piece-tokenizer.cc), a speech toolkit for on-device recognition, synthesis and speaker diarization
|
||||||
- [**stable-diffusion.cpp**](https://github.com/leejet/stable-diffusion.cpp), a C++ implementation of the Stable Diffusion image generation model
|
- [**stable-diffusion.cpp**](https://github.com/leejet/stable-diffusion.cpp), a C++ implementation of the Stable Diffusion image generation model
|
||||||
- [**TanvasTouch**](https://tanvas.co/tanvastouch-sdk-third-party-acknowledgments), a software development kit (SDK) that enables developers to create tactile experiences on touchscreens, allowing users to feel textures and physical sensations in a digital environment
|
- [**TanvasTouch**](https://tanvas.co/tanvastouch-sdk-third-party-acknowledgments), a software development kit (SDK) that enables developers to create tactile experiences on touchscreens, allowing users to feel textures and physical sensations in a digital environment
|
||||||
- [**TensorFlow**](https://github.com/tensorflow/tensorflow), a machine learning framework that facilitates the development and training of models, supporting data serialization and efficient data exchange between components
|
- [**TensorFlow**](https://github.com/tensorflow/tensorflow), a machine learning framework that facilitates the development and training of models, supporting data serialization and efficient data exchange between components
|
||||||
|
- [**whisper.cpp**](https://github.com/ggml-org/whisper.cpp), a C++ implementation of OpenAI's Whisper automatic speech recognition model
|
||||||
|
|
||||||
## Scientific Research and Analysis
|
## Scientific Research and Analysis
|
||||||
|
|
||||||
- [**BLACK**](https://www.black-sat.org/en/stable/installation/linux.html), a bounded linear temporal logic (LTL) satisfiability checker
|
- [**BLACK**](https://www.black-sat.org/en/stable/installation/linux.html), a bounded linear temporal logic (LTL) satisfiability checker
|
||||||
|
- [**CERN ALICE O2**](https://github.com/AliceO2Group/AliceO2), the online-offline computing framework of the ALICE heavy-ion experiment at the Large Hadron Collider
|
||||||
- [**CERN Atlas Athena**](https://gitlab.cern.ch/atlas/athena/-/blob/main/Control/PerformanceMonitoring/PerfMonComps/src/PerfMonMTSvc.h), a software framework used in the ATLAS experiment at the Large Hadron Collider (LHC) for performance monitoring
|
- [**CERN Atlas Athena**](https://gitlab.cern.ch/atlas/athena/-/blob/main/Control/PerformanceMonitoring/PerfMonComps/src/PerfMonMTSvc.h), a software framework used in the ATLAS experiment at the Large Hadron Collider (LHC) for performance monitoring
|
||||||
|
- [**CERN CMSSW**](https://github.com/cms-sw/cmssw), the offline software framework of the CMS experiment at the Large Hadron Collider
|
||||||
|
- [**CERN Gaudi**](https://gitlab.cern.ch/gaudi/Gaudi), the event-processing framework used by the LHCb and ATLAS experiments at the Large Hadron Collider
|
||||||
- [**ICU**](https://github.com/unicode-org/icu), the International Components for Unicode, a mature library for software globalization and multilingual support
|
- [**ICU**](https://github.com/unicode-org/icu), the International Components for Unicode, a mature library for software globalization and multilingual support
|
||||||
- [**KAMERA**](https://github.com/Kitware/kamera), a platform for synchronized data collection and real-time deep learning to map marine species like polar bears and seals, aiding Arctic ecosystem research
|
- [**KAMERA**](https://github.com/Kitware/kamera), a platform for synchronized data collection and real-time deep learning to map marine species like polar bears and seals, aiding Arctic ecosystem research
|
||||||
- [**KiCad**](https://gitlab.com/kicad/code/kicad/-/tree/master/thirdparty/nlohmann_json), a free and open-source software suite for electronic design automation
|
- [**KiCad**](https://gitlab.com/kicad/code/kicad/-/tree/master/thirdparty/nlohmann_json), a free and open-source software suite for electronic design automation
|
||||||
|
- [**LLNL ROSE**](https://github.com/llnl/rose), a compiler infrastructure from Lawrence Livermore National Laboratory for building source-to-source program analysis and transformation tools
|
||||||
- [**Maple**](https://www.maplesoft.com/support/help/Maple/view.aspx?path=copyright), a symbolic and numeric computing environment for advanced mathematical modeling and analysis
|
- [**Maple**](https://www.maplesoft.com/support/help/Maple/view.aspx?path=copyright), a symbolic and numeric computing environment for advanced mathematical modeling and analysis
|
||||||
- [**MeVisLab**](https://mevislabdownloads.mevis.de/docs/current/MeVis/ThirdParty/Documentation/Publish/ThirdPartyReference/index.html), a software framework for medical image processing and visualization.
|
- [**MeVisLab**](https://mevislabdownloads.mevis.de/docs/current/MeVis/ThirdParty/Documentation/Publish/ThirdPartyReference/index.html), a software framework for medical image processing and visualization.
|
||||||
|
- [**MITK**](https://github.com/MITK/MITK), the Medical Imaging Interaction Toolkit, a framework for developing interactive medical image processing software
|
||||||
- [**OpenPMD API**](https://openpmd-api.readthedocs.io/en/0.8.0-alpha/backends/json.html), a versatile programming interface for accessing and managing scientific data, designed to facilitate the efficient storage, retrieval, and sharing of simulation data across various applications and platforms
|
- [**OpenPMD API**](https://openpmd-api.readthedocs.io/en/0.8.0-alpha/backends/json.html), a versatile programming interface for accessing and managing scientific data, designed to facilitate the efficient storage, retrieval, and sharing of simulation data across various applications and platforms
|
||||||
|
- [**ORNL DataFed**](https://github.com/ORNL/DataFed), a federated scientific data management system developed at Oak Ridge National Laboratory
|
||||||
- [**ParaView**](https://github.com/Kitware/ParaView), an open-source tool for large-scale data visualization and analysis across various scientific domains
|
- [**ParaView**](https://github.com/Kitware/ParaView), an open-source tool for large-scale data visualization and analysis across various scientific domains
|
||||||
- [**QGIS**](https://gitlab.b-data.ch/qgis/qgis/-/blob/backport-57658-to-release-3_34/external/nlohmann/json.hpp), a free and open-source geographic information system (GIS) application that allows users to create, edit, visualize, and analyze geospatial data across a variety of formats
|
- [**QGIS**](https://gitlab.b-data.ch/qgis/qgis/-/blob/backport-57658-to-release-3_34/external/nlohmann/json.hpp), a free and open-source geographic information system (GIS) application that allows users to create, edit, visualize, and analyze geospatial data across a variety of formats
|
||||||
- [**VTK**](https://github.com/Kitware/VTK), a software library for 3D computer graphics, image processing, and visualization
|
- [**Sandia InterSpec**](https://github.com/sandialabs/InterSpec), spectral radiation analysis software from Sandia National Laboratories for identifying radioactive isotopes
|
||||||
- [**VolView**](https://github.com/Kitware/VolView), a lightweight application for interactive visualization and analysis of 3D medical imaging data.
|
- [**VolView**](https://github.com/Kitware/VolView), a lightweight application for interactive visualization and analysis of 3D medical imaging data.
|
||||||
|
- [**VTK**](https://github.com/Kitware/VTK), a software library for 3D computer graphics, image processing, and visualization
|
||||||
|
|
||||||
## Business and Productivity Software
|
## Business and Productivity Software
|
||||||
|
|
||||||
- [**ArcGIS PRO**](https://www.esri.com/content/dam/esrisites/en-us/media/legal/open-source-acknowledgements/arcgis-pro-2-8-attribution-report.html), a desktop geographic information system (GIS) application developed by Esri for mapping and spatial analysis
|
- [**ArcGIS PRO**](https://www.esri.com/content/dam/esrisites/en-us/media/legal/open-source-acknowledgements/arcgis-pro-2-8-attribution-report.html), a desktop geographic information system (GIS) application developed by Esri for mapping and spatial analysis
|
||||||
- [**Autodesk Desktop**](https://damassets.autodesk.net/content/dam/autodesk/www/Company/legal-notices-trademarks/autodesk-desktop-platform-components/internal-autodesk-components-web-page-2023.pdf), a software platform developed by Autodesk for creating and managing desktop applications and services
|
- [**Autodesk Desktop**](https://damassets.autodesk.net/content/dam/autodesk/www/Company/legal-notices-trademarks/autodesk-desktop-platform-components/internal-autodesk-components-web-page-2023.pdf), a software platform developed by Autodesk for creating and managing desktop applications and services
|
||||||
- [**Check Point**](https://www.checkpoint.com/about-us/copyright-and-trademarks/), a cybersecurity company specializing in threat prevention and network security solutions, offering a range of products designed to protect enterprises from cyber threats and ensure data integrity
|
- [**Check Point**](https://www.checkpoint.com/about-us/copyright-and-trademarks/), a cybersecurity company specializing in threat prevention and network security solutions, offering a range of products designed to protect enterprises from cyber threats and ensure data integrity
|
||||||
|
- [**EasyEffects**](https://github.com/wwmm/easyeffects/blob/master/src/presets_manager.hpp), an audio effects processor for PipeWire offering limiting, compression and equalization
|
||||||
|
- [**espanso**](https://github.com/espanso/espanso/blob/dev/espanso-ui/src/win32/native.cpp), a cross-platform text expander
|
||||||
|
- [**Karabiner-Elements**](https://github.com/pqrs-org/Karabiner-Elements/blob/main/src/share/app_icon.hpp), a keyboard customizer for macOS
|
||||||
|
- [**MacType**](https://github.com/snowie2000/mactype/blob/directwrite/settings.h), a font rendering engine for Windows
|
||||||
|
- [**magicplan**](https://help.magicplan.app/acknowledgments), a mobile application for creating floor plans and interior designs using augmented reality
|
||||||
- [**Microsoft Office for Mac**](https://officecdnmac.microsoft.com/pr/legal/mac/OfficeforMacAttributions.html), a suite of productivity applications developed by Microsoft for macOS, including tools for word processing, spreadsheets, and presentations
|
- [**Microsoft Office for Mac**](https://officecdnmac.microsoft.com/pr/legal/mac/OfficeforMacAttributions.html), a suite of productivity applications developed by Microsoft for macOS, including tools for word processing, spreadsheets, and presentations
|
||||||
- [**Microsoft Teams**](https://www.microsoft.com/microsoft-teams/), a team collaboration application offering workspace chat and video conferencing, file storage, and integration of proprietary and third-party applications and services
|
- [**Microsoft Teams**](https://www.microsoft.com/microsoft-teams/), a team collaboration application offering workspace chat and video conferencing, file storage, and integration of proprietary and third-party applications and services
|
||||||
|
- [**MuseScore**](https://github.com/musescore/MuseScore), a free and open-source music notation and composition application
|
||||||
|
- [**NanaZip**](https://github.com/M2Team/NanaZip/blob/main/NanaZip.Codecs/NanaZip.Codecs.Archive.ElectronAsar.cpp), a 7-Zip derivative built for modern Windows
|
||||||
- [**Nexthink Infinity**](https://docs.nexthink.com/legal/services-terms/experience-open-source-software-licenses/infinity-2022.8-software-licenses), a digital employee experience management platform for monitoring and improving IT performance
|
- [**Nexthink Infinity**](https://docs.nexthink.com/legal/services-terms/experience-open-source-software-licenses/infinity-2022.8-software-licenses), a digital employee experience management platform for monitoring and improving IT performance
|
||||||
- [**Sophos Connect Client**](https://docs.sophos.com/nsg/licenses/SophosConnect/SophosConnectAttribution.html), a secure VPN client from Sophos that allows remote users to connect to their corporate network, ensuring secure access to resources and data
|
- [**Sophos Connect Client**](https://docs.sophos.com/nsg/licenses/SophosConnect/SophosConnectAttribution.html), a secure VPN client from Sophos that allows remote users to connect to their corporate network, ensuring secure access to resources and data
|
||||||
- [**Stonebranch**](https://stonebranchdocs.atlassian.net/wiki/spaces/UA77/pages/799545647/Licenses+for+Third-Party+Libraries), a cloud-based cybersecurity solution that integrates backup, disaster recovery, and cybersecurity features to protect data and ensure business continuity for organizations
|
- [**Stonebranch**](https://stonebranchdocs.atlassian.net/wiki/spaces/UA77/pages/799545647/Licenses+for+Third-Party+Libraries), a cloud-based cybersecurity solution that integrates backup, disaster recovery, and cybersecurity features to protect data and ensure business continuity for organizations
|
||||||
- [**Tablecruncher**](https://tablecruncher.com/), a data analysis tool that allows users to import, analyze, and visualize spreadsheet data, offering interactive features for better insights and decision-making
|
- [**Tablecruncher**](https://tablecruncher.com/), a data analysis tool that allows users to import, analyze, and visualize spreadsheet data, offering interactive features for better insights and decision-making
|
||||||
- [**magicplan**](https://help.magicplan.app/acknowledgments), a mobile application for creating floor plans and interior designs using augmented reality
|
- [**VNote**](https://github.com/vnotex/vnote/blob/master/src/core/services/notebookcoreservice.cpp), a Markdown-based note-taking application written in C++
|
||||||
|
|
||||||
## Databases and Big Data
|
## Databases and Big Data
|
||||||
|
|
||||||
- [**ADIOS2**](https://code.ornl.gov/ecpcitest/adios2/-/tree/pr4285_FFSUpstream/thirdparty/nlohmann_json?ref_type=heads), a data management framework designed for high-performance input and output operations
|
- [**ADIOS2**](https://code.ornl.gov/ecpcitest/adios2/-/tree/pr4285_FFSUpstream/thirdparty/nlohmann_json?ref_type=heads), a data management framework designed for high-performance input and output operations
|
||||||
|
- [**Apache Doris**](https://github.com/apache/doris/blob/master/be/src/runtime/be_proc_monitor.cpp), a real-time analytical database for high-concurrency queries
|
||||||
|
- [**Claris FileMaker Server**](https://www.claris.com/company/legal/docs/acknowledgements/filemaker-server-macwin/claris_fms2025_acknowledgements_en.pdf), the server platform hosting FileMaker custom apps and databases, developed by Apple subsidiary Claris
|
||||||
|
- [**ClickHouse**](https://github.com/ClickHouse/ClickHouse), a column-oriented database management system for real-time analytical queries
|
||||||
- [**Cribl Stream**](https://docs.cribl.io/stream/third-party-current-list/), a real-time data processing platform that enables organizations to collect, route, and transform observability data, enhancing visibility and insights into their systems
|
- [**Cribl Stream**](https://docs.cribl.io/stream/third-party-current-list/), a real-time data processing platform that enables organizations to collect, route, and transform observability data, enhancing visibility and insights into their systems
|
||||||
- [**DB Browser for SQLite**](https://github.com/sqlitebrowser/sqlitebrowser), a visual open-source tool for creating, designing, and editing SQLite database files
|
- [**DB Browser for SQLite**](https://github.com/sqlitebrowser/sqlitebrowser), a visual open-source tool for creating, designing, and editing SQLite database files
|
||||||
|
- [**Manticore Search**](https://github.com/manticoresoftware/manticoresearch/blob/main/src/searchdhttpcompat.cpp), a database for search, offering full-text and vector queries
|
||||||
|
- [**Milvus**](https://github.com/milvus-io/milvus/blob/master/internal/core/src/query/PlanImpl.h), a cloud-native vector database built for embedding similarity search
|
||||||
|
- [**MongoDB**](https://github.com/mongodb/mongo/blob/master/src/mongo/replay/config_handler.cpp), a general-purpose document database
|
||||||
- [**MySQL Connector/C++**](https://docs.oracle.com/cd/E17952_01/connector-cpp-9.1-license-com-en/license-opentelemetry-cpp-com.html), a C++ library for connecting and interacting with MySQL databases
|
- [**MySQL Connector/C++**](https://docs.oracle.com/cd/E17952_01/connector-cpp-9.1-license-com-en/license-opentelemetry-cpp-com.html), a C++ library for connecting and interacting with MySQL databases
|
||||||
- [**MySQL NDB Cluster**](https://downloads.mysql.com/docs/licenses/cluster-9.0-com-en.pdf), a distributed database system that provides high availability and scalability for MySQL databases
|
- [**MySQL NDB Cluster**](https://downloads.mysql.com/docs/licenses/cluster-9.0-com-en.pdf), a distributed database system that provides high availability and scalability for MySQL databases
|
||||||
- [**MySQL Shell**](https://downloads.mysql.com/docs/licenses/mysql-shell-8.0-gpl-en.pdf), an advanced client and code editor for interacting with MySQL servers, supporting SQL, Python, and JavaScript
|
- [**MySQL Shell**](https://downloads.mysql.com/docs/licenses/mysql-shell-8.0-gpl-en.pdf), an advanced client and code editor for interacting with MySQL servers, supporting SQL, Python, and JavaScript
|
||||||
- [**PrestoDB**](https://github.com/prestodb/presto), a distributed SQL query engine designed for large-scale data analytics, originally developed by Facebook
|
- [**PrestoDB**](https://github.com/prestodb/presto/blob/master/presto-native-execution/presto_cpp/main/Announcer.cpp), a distributed SQL query engine designed for large-scale data analytics, originally developed by Facebook
|
||||||
- [**ROOT Data Analysis Framework**](https://root.cern/doc/v614/classnlohmann_1_1basic__json.html), an open-source data analysis framework widely used in high-energy physics and other fields for data processing and visualization
|
- [**ROOT Data Analysis Framework**](https://root.cern/doc/v614/classnlohmann_1_1basic__json.html), an open-source data analysis framework widely used in high-energy physics and other fields for data processing and visualization
|
||||||
|
- [**Typesense**](https://github.com/typesense/typesense/blob/v31/include/join.h), an open source typo-tolerant search engine
|
||||||
|
- [**Vearch**](https://github.com/jd-opensource/vearch), a distributed vector database developed at JD.com for similarity search and retrieval-augmented generation
|
||||||
- [**WiredTiger**](https://github.com/wiredtiger/wiredtiger), a high-performance storage engine for databases, offering support for compression, concurrency, and checkpointing
|
- [**WiredTiger**](https://github.com/wiredtiger/wiredtiger), a high-performance storage engine for databases, offering support for compression, concurrency, and checkpointing
|
||||||
|
|
||||||
## Simulation and Modeling
|
## Simulation and Modeling
|
||||||
|
|
||||||
|
- [**Adobe Lagrange**](https://github.com/adobe/lagrange), a geometry processing library developed by Adobe for mesh manipulation and analysis
|
||||||
- [**Arcturus HoloSuite**](https://www.datocms-assets.com/104353/1698904597-holosuite-third-party-software-credits-and-attributions-2.pdf), a software toolset for capturing, editing, and streaming volumetric video, featuring advanced compression technologies for high-quality 3D content creation
|
- [**Arcturus HoloSuite**](https://www.datocms-assets.com/104353/1698904597-holosuite-third-party-software-credits-and-attributions-2.pdf), a software toolset for capturing, editing, and streaming volumetric video, featuring advanced compression technologies for high-quality 3D content creation
|
||||||
- [**azul**](https://pure.tudelft.nl/ws/files/85338589/tgis.12673.pdf), a fast and efficient 3D city model viewer designed for visualizing urban environments and spatial data
|
- [**azul**](https://pure.tudelft.nl/ws/files/85338589/tgis.12673.pdf), a fast and efficient 3D city model viewer designed for visualizing urban environments and spatial data
|
||||||
|
- [**Bambu Studio**](https://github.com/bambulab/BambuStudio), a slicing and print management application for Bambu Lab 3D printers
|
||||||
- [**Blender**](https://projects.blender.org/blender/blender/search?q=nlohmann), a free and open-source 3D creation suite for modeling, animation, rendering, and more
|
- [**Blender**](https://projects.blender.org/blender/blender/search?q=nlohmann), a free and open-source 3D creation suite for modeling, animation, rendering, and more
|
||||||
- [**cpplot**](https://cpplot.readthedocs.io/en/latest/library_api/function_eigen_8h_1ac080eac0541014c5892a55e41bf785e6.html), a library for creating interactive graphs and charts in C++, which can be viewed in web browsers
|
- [**cpplot**](https://cpplot.readthedocs.io/en/latest/library_api/function_eigen_8h_1ac080eac0541014c5892a55e41bf785e6.html), a library for creating interactive graphs and charts in C++, which can be viewed in web browsers
|
||||||
- [**Foundry Nuke**](https://learn.foundry.com/nuke/content/misc/studio_third_party_libraries.html), a powerful node-based digital compositing and visual effects application used in film and television post-production
|
- [**Foundry Nuke**](https://learn.foundry.com/nuke/content/misc/studio_third_party_libraries.html), a powerful node-based digital compositing and visual effects application used in film and television post-production
|
||||||
|
- [**FreeCAD**](https://github.com/FreeCAD/FreeCAD), a free and open-source parametric 3D CAD modeler for product design and engineering
|
||||||
- [**GAMS**](https://www.gams.com/47/docs/THIRDPARTY.html), a high-performance mathematical modeling system for optimization and decision support
|
- [**GAMS**](https://www.gams.com/47/docs/THIRDPARTY.html), a high-performance mathematical modeling system for optimization and decision support
|
||||||
|
- [**Keysight WirelessPro**](https://docs.keysight.com/display/engdocwirelesspro/WirelessPro+2026+Release+Notes), a simulation platform for 5G, 5G-Advanced, and 6G cellular network research
|
||||||
- [**Kitware SMTK**](https://github.com/Kitware/SMTK), a software toolkit for managing simulation models and workflows in scientific and engineering applications
|
- [**Kitware SMTK**](https://github.com/Kitware/SMTK), a software toolkit for managing simulation models and workflows in scientific and engineering applications
|
||||||
- [**M-Star**](https://docs.mstarcfd.com/3_Licensing/thirdparty-licenses.html), a computational fluid dynamics software for simulating and analyzing fluid flow
|
- [**M-Star**](https://docs.mstarcfd.com/3_Licensing/thirdparty-licenses.html), a computational fluid dynamics software for simulating and analyzing fluid flow
|
||||||
- [**MapleSim CAD Toolbox**](https://www.maplesoft.com/support/help/MapleSim/view.aspx?path=CADToolbox/copyright), a software extension for MapleSim that integrates CAD models, allowing users to import, manipulate, and analyze 3D CAD data within the MapleSim environment for enhanced modeling and simulation
|
- [**MapleSim CAD Toolbox**](https://www.maplesoft.com/support/help/MapleSim/view.aspx?path=CADToolbox/copyright), a software extension for MapleSim that integrates CAD models, allowing users to import, manipulate, and analyze 3D CAD data within the MapleSim environment for enhanced modeling and simulation
|
||||||
|
- [**Microsoft AirSim**](https://github.com/microsoft/AirSim/blob/main/AirLib/include/common/Settings.hpp), a simulator for autonomous vehicles and drones built on Unreal Engine
|
||||||
- [**NVIDIA Omniverse**](https://docs.omniverse.nvidia.com/composer/latest/common/product-licenses/usd-explorer/usd-explorer-2023.2.0-licenses-manifest.html), a platform for 3D content creation and collaboration that enables real-time simulations and interactive experiences across various industries
|
- [**NVIDIA Omniverse**](https://docs.omniverse.nvidia.com/composer/latest/common/product-licenses/usd-explorer/usd-explorer-2023.2.0-licenses-manifest.html), a platform for 3D content creation and collaboration that enables real-time simulations and interactive experiences across various industries
|
||||||
|
- [**OpenSCAD**](https://github.com/openscad/openscad/blob/master/src/core/AIClient.cc), a script-driven solid 3D CAD modeller
|
||||||
|
- [**OrcaSlicer**](https://github.com/SoftFever/OrcaSlicer), an open-source slicer supporting a wide range of consumer 3D printers
|
||||||
- [**Pixar Renderman**](https://rmanwiki-26.pixar.com/space/REN26/19662083/Legal+Notice), a photorealistic 3D rendering software developed by Pixar, widely used in the film industry for creating high-quality visual effects and animations
|
- [**Pixar Renderman**](https://rmanwiki-26.pixar.com/space/REN26/19662083/Legal+Notice), a photorealistic 3D rendering software developed by Pixar, widely used in the film industry for creating high-quality visual effects and animations
|
||||||
|
- [**PrusaSlicer**](https://github.com/prusa3d/PrusaSlicer), the slicing software developed by Prusa Research for its 3D printers
|
||||||
- [**ROS - Robot Operating System**](http://docs.ros.org/en/noetic/api/behaviortree_cpp/html/json_8hpp_source.html), a set of software libraries and tools that assist in developing robot applications
|
- [**ROS - Robot Operating System**](http://docs.ros.org/en/noetic/api/behaviortree_cpp/html/json_8hpp_source.html), a set of software libraries and tools that assist in developing robot applications
|
||||||
- [**UBS**](https://www.ubs.com/), a multinational financial services and banking company
|
- [**UBS**](https://www.ubs.com/), a multinational financial services and banking company
|
||||||
|
|
||||||
@@ -161,18 +278,31 @@ the result of an internet search. If you know further customers of the library,
|
|||||||
- [**Acronis Cyber Protect Cloud**](https://care.acronis.com/s/article/59533-Third-party-software-used-in-Acronis-Cyber-Protect-Cloud?language=en_US), an all-in-one data protection solution that combines backup, disaster recovery, and cybersecurity to safeguard business data from threats like ransomware
|
- [**Acronis Cyber Protect Cloud**](https://care.acronis.com/s/article/59533-Third-party-software-used-in-Acronis-Cyber-Protect-Cloud?language=en_US), an all-in-one data protection solution that combines backup, disaster recovery, and cybersecurity to safeguard business data from threats like ransomware
|
||||||
- [**Baereos**](https://gitlab.tiger-computing.co.uk/packages/bareos/-/blob/tiger/bullseye/third-party/CLI11/examples/json.cpp), a backup solution that provides data protection and recovery options for various environments, including physical and virtual systems
|
- [**Baereos**](https://gitlab.tiger-computing.co.uk/packages/bareos/-/blob/tiger/bullseye/third-party/CLI11/examples/json.cpp), a backup solution that provides data protection and recovery options for various environments, including physical and virtual systems
|
||||||
- [**Bitdefender Home Scanner**](https://www.bitdefender.de/site/Main/view/home-scanner-open-source.html), a tool from Bitdefender that scans devices for malware and security threats, providing a safeguard against potential online dangers
|
- [**Bitdefender Home Scanner**](https://www.bitdefender.de/site/Main/view/home-scanner-open-source.html), a tool from Bitdefender that scans devices for malware and security threats, providing a safeguard against potential online dangers
|
||||||
|
- [**Cisco MLS++**](https://github.com/cisco/mlspp), an implementation of the Messaging Layer Security protocol for end-to-end encrypted group messaging
|
||||||
- [**Citrix Provisioning**](https://docs.citrix.com/en-us/provisioning/2203-ltsr/downloads/pvs-third-party-notices-2203.pdf), a solution that streamlines the delivery of virtual desktops and applications by allowing administrators to manage and provision resources efficiently across multiple environments
|
- [**Citrix Provisioning**](https://docs.citrix.com/en-us/provisioning/2203-ltsr/downloads/pvs-third-party-notices-2203.pdf), a solution that streamlines the delivery of virtual desktops and applications by allowing administrators to manage and provision resources efficiently across multiple environments
|
||||||
- [**Citrix Virtual Apps and Desktops**](https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/2305/downloads/third-party-notices-apps-and-desktops.pdf), a solution from Citrix that delivers virtual apps and desktops
|
- [**Citrix Virtual Apps and Desktops**](https://docs.citrix.com/en-us/citrix-virtual-apps-desktops/2305/downloads/third-party-notices-apps-and-desktops.pdf), a solution from Citrix that delivers virtual apps and desktops
|
||||||
- [**Cyberarc**](https://docs.cyberark.com/Downloads/Legal/Privileged%20Session%20Manager%20for%20SSH%20Third-Party%20Notices.pdf), a security solution that specializes in privileged access management, enabling organizations to control and monitor access to critical systems and data, thereby enhancing overall cybersecurity posture
|
- [**Cyberarc**](https://docs.cyberark.com/Downloads/Legal/Privileged%20Session%20Manager%20for%20SSH%20Third-Party%20Notices.pdf), a security solution that specializes in privileged access management, enabling organizations to control and monitor access to critical systems and data, thereby enhancing overall cybersecurity posture
|
||||||
|
- [**Deutsche Telekom sysrepo-plugins**](https://github.com/telekom/sysrepo-plugins), a collection of YANG datastore plugins used to manage network devices
|
||||||
- [**Egnyte Desktop**](https://helpdesk.egnyte.com/hc/en-us/articles/360007071732-Third-Party-Software-Acknowledgements), a secure cloud storage solution designed for businesses, enabling file sharing, collaboration, and data management across teams while ensuring compliance and data protection
|
- [**Egnyte Desktop**](https://helpdesk.egnyte.com/hc/en-us/articles/360007071732-Third-Party-Software-Acknowledgements), a secure cloud storage solution designed for businesses, enabling file sharing, collaboration, and data management across teams while ensuring compliance and data protection
|
||||||
- [**Elster**](https://www.secunet.com/en/about-us/press/article/elstersecure-bietet-komfortablen-login-ohne-passwort-dank-secunet-protect4use), a digital platform developed by German tax authorities for secure and efficient electronic tax filing and management using secunet protect4use
|
- [**Elster**](https://www.secunet.com/en/about-us/press/article/elstersecure-bietet-komfortablen-login-ohne-passwort-dank-secunet-protect4use), a digital platform developed by German tax authorities for secure and efficient electronic tax filing and management using secunet protect4use
|
||||||
|
- [**Envoy**](https://github.com/envoyproxy/envoy), a cloud-native edge and service proxy that forms the data plane of many service meshes
|
||||||
- [**Ethereum Solidity**](https://github.com/ethereum/solidity), a high-level, object-oriented programming language designed for implementing smart contracts on the Ethereum platform
|
- [**Ethereum Solidity**](https://github.com/ethereum/solidity), a high-level, object-oriented programming language designed for implementing smart contracts on the Ethereum platform
|
||||||
|
- [**gVisor**](https://github.com/google/gvisor), an application kernel that provides a secure sandbox for running untrusted containers
|
||||||
|
- [**IBM Storage Virtualize**](https://public.dhe.ibm.com/systems/support/warranty/pdfs/stgoilc/SV_for_FS_7300_v8_7_0_Base_OILC.pdf), the software powering IBM FlashSystem enterprise storage arrays
|
||||||
- [**Inciga**](https://fossies.org/linux/icinga2/third-party/nlohmann_json/json.hpp), a monitoring tool for IT infrastructure, designed to provide insights into system performance and availability through customizable dashboards and alerts
|
- [**Inciga**](https://fossies.org/linux/icinga2/third-party/nlohmann_json/json.hpp), a monitoring tool for IT infrastructure, designed to provide insights into system performance and availability through customizable dashboards and alerts
|
||||||
- [**Intel Accelerator Management Daemon for VMware ESXi**](https://downloadmirror.intel.com/772507/THIRD-PARTY.txt), a management tool designed for monitoring and controlling Intel hardware accelerators within VMware ESXi environments, optimizing performance and resource allocation
|
- [**Intel Accelerator Management Daemon for VMware ESXi**](https://downloadmirror.intel.com/772507/THIRD-PARTY.txt), a management tool designed for monitoring and controlling Intel hardware accelerators within VMware ESXi environments, optimizing performance and resource allocation
|
||||||
- [**Juniper Identity Management Service**](https://www.juniper.net/documentation/us/en/software/jims/jims-guide/jims-guide.pdf)
|
- [**Juniper Identity Management Service**](https://www.juniper.net/documentation/us/en/software/jims/jims-guide/jims-guide.pdf)
|
||||||
|
- [**Meta FBOSS**](https://github.com/facebook/fboss), the software stack that controls the network switches in Meta's data centers
|
||||||
- [**Microsoft Azure IoT SDK**](https://library.e.abb.com/public/2779c5f85f30484192eb3cb3f666a201/IP%20Gateway%20Open%20License%20Declaration_9AKK108467A4095_Rev_C.pdf), a collection of tools and libraries to help developers connect, build, and deploy Internet of Things (IoT) solutions on the Azure cloud platform
|
- [**Microsoft Azure IoT SDK**](https://library.e.abb.com/public/2779c5f85f30484192eb3cb3f666a201/IP%20Gateway%20Open%20License%20Declaration_9AKK108467A4095_Rev_C.pdf), a collection of tools and libraries to help developers connect, build, and deploy Internet of Things (IoT) solutions on the Azure cloud platform
|
||||||
|
- [**Microsoft Confidential Consortium Framework**](https://github.com/microsoft/CCF), a framework for building secure, highly available applications on trusted execution environments
|
||||||
- [**Microsoft WinGet**](https://github.com/microsoft/winget-cli), a command-line utility included in the Windows Package Manager
|
- [**Microsoft WinGet**](https://github.com/microsoft/winget-cli), a command-line utility included in the Windows Package Manager
|
||||||
|
- [**Mitsubishi Electric SECS/GEM**](https://dl.mitsubishielectric.com/dl/fa/document/manual/plc/sh082483eng/sh082483engi.pdf), the semiconductor equipment communication software running on Mitsubishi Electric C Controller and C intelligent function modules
|
||||||
|
- [**Moxa**](https://www.moxa.com/getmedia/fbe2a0c7-8dda-4b5b-a501-15e45adebb1f/moxa-foss-statement-for-da-720-series-win-10-ltsc-21h2-declaration-v1.0.pdf), a provider of industrial networking, computing, and automation infrastructure
|
||||||
- [**plexusAV**](https://www.sisme.com/media/10994/manual_plexusav-p-avn-4-form8244-c.pdf), a high-performance AV-over-IP transceiver device capable of video encoding and decoding using the IPMX standard
|
- [**plexusAV**](https://www.sisme.com/media/10994/manual_plexusav-p-avn-4-form8244-c.pdf), a high-performance AV-over-IP transceiver device capable of video encoding and decoding using the IPMX standard
|
||||||
- [**Pointr**](https://docs-dev.pointr.tech/docs/8.x/Developer%20Portal/Open%20Source%20Licenses/), a platform for indoor positioning and navigation solutions, offering tools and SDKs for developers to create location-based applications
|
- [**Pointr**](https://docs-dev.pointr.tech/docs/8.x/Developer%20Portal/Open%20Source%20Licenses/), a platform for indoor positioning and navigation solutions, offering tools and SDKs for developers to create location-based applications
|
||||||
- [**secunet protect4use**](https://www.secunet.com/en/about-us/press/article/elstersecure-bietet-komfortablen-login-ohne-passwort-dank-secunet-protect4use), a secure, passwordless multifactor authentication solution that transforms smartphones into digital keyrings, ensuring high security for online services and digital identities
|
- [**secunet protect4use**](https://www.secunet.com/en/about-us/press/article/elstersecure-bietet-komfortablen-login-ohne-passwort-dank-secunet-protect4use), a secure, passwordless multifactor authentication solution that transforms smartphones into digital keyrings, ensuring high security for online services and digital identities
|
||||||
- [**Sencore MRD 7000**](https://www.foccusdigital.com/wp-content/uploads/2025/03/MRD-7000-Manual-8175V.pdf), a professional multi-channel receiver and decoder supporting UHD and HD stream decoding
|
- [**Sencore MRD 7000**](https://www.foccusdigital.com/wp-content/uploads/2025/03/MRD-7000-Manual-8175V.pdf), a professional multi-channel receiver and decoder supporting UHD and HD stream decoding
|
||||||
|
- [**Siemens SINEC**](https://cache.industry.siemens.com/dl/files/917/109974917/att_1298783/v2/OSS_SINEC-NMS_99.pdf), a family of network management and infrastructure services for industrial networks
|
||||||
|
- [**Toshiba Industrial Servers**](https://www.global.toshiba/content/dam/toshiba/jp/products-solutions/industrial/computer/product/server/fs20000r/pdf/FS20000R_OSS_License_6E8C5817_rev0.pdf), the FS20000R series of industrial servers for factory automation and control systems
|
||||||
|
- [**Wazuh**](https://github.com/wazuh/wazuh/blob/main/src/data_provider/src/sysInfo.cpp), a security platform for threat detection, integrity monitoring and incident response
|
||||||
|
- [**ZeroTier**](https://github.com/zerotier/ZeroTierOne/blob/dev/osdep/OSUtils.hpp), a software-defined networking service that creates virtual Ethernet networks
|
||||||
|
|||||||
@@ -291,9 +291,10 @@ A JSON Pointer array index must be a number.
|
|||||||
|
|
||||||
### json.exception.parse_error.110
|
### json.exception.parse_error.110
|
||||||
|
|
||||||
When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
|
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
|
||||||
|
been read.
|
||||||
|
|
||||||
!!! failure "Example message"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input
|
[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input
|
||||||
@@ -301,6 +302,9 @@ When parsing CBOR or MessagePack, the byte vector ends before the complete value
|
|||||||
```
|
```
|
||||||
[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A
|
[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.parse_error.112
|
### json.exception.parse_error.112
|
||||||
|
|
||||||
@@ -329,10 +333,14 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
|||||||
```
|
```
|
||||||
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
|
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.parse_error.113
|
### json.exception.parse_error.113
|
||||||
|
|
||||||
While parsing a map key, a value that is not a string has been read.
|
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
||||||
|
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
|
||||||
|
|
||||||
!!! failure "Example messages"
|
!!! failure "Example messages"
|
||||||
|
|
||||||
@@ -345,6 +353,9 @@ While parsing a map key, a value that is not a string has been read.
|
|||||||
```
|
```
|
||||||
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.parse_error.114
|
### json.exception.parse_error.114
|
||||||
|
|
||||||
@@ -853,13 +864,30 @@ and this exception no longer occurs.
|
|||||||
|
|
||||||
### json.exception.out_of_range.408
|
### json.exception.out_of_range.408
|
||||||
|
|
||||||
The size (following `#`) of an UBJSON array or object exceeds the maximal capacity.
|
The size of an array or object in a [binary format](../features/binary_formats/index.md) exceeds the maximal capacity:
|
||||||
|
the size following `#` for [UBJSON](../features/binary_formats/ubjson.md)/[BJData](../features/binary_formats/bjdata.md),
|
||||||
|
or the encoded length for [CBOR](../features/binary_formats/cbor.md).
|
||||||
|
|
||||||
!!! failure "Example message"
|
The exception is also thrown for a [UBJSON](../features/binary_formats/ubjson.md) array of a type that is encoded by its
|
||||||
|
marker alone (`Z`, `T` or `F`) whose declared count exceeds 1,048,576 (`1 << 20`). Such an array has no payload, so its
|
||||||
|
count alone decides how much memory is allocated, and a handful of bytes would otherwise describe billions of values.
|
||||||
|
[`to_ubjson`](../api/basic_json/to_ubjson.md) writes longer arrays of these types without the size and type annotation,
|
||||||
|
so any value it produces can still be read back.
|
||||||
|
|
||||||
|
!!! failure "Example messages"
|
||||||
|
|
||||||
```
|
```
|
||||||
excessive array size: 8658170730974374167
|
excessive array size: 8658170730974374167
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size
|
||||||
|
```
|
||||||
|
```
|
||||||
|
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size
|
||||||
|
```
|
||||||
|
```
|
||||||
|
[json.exception.out_of_range.408] syntax error while parsing UBJSON size: excessive array size
|
||||||
|
```
|
||||||
|
|
||||||
### json.exception.out_of_range.409
|
### json.exception.out_of_range.409
|
||||||
|
|
||||||
@@ -898,6 +926,50 @@ A JSON Patch `add` operation cannot be applied because the target location's par
|
|||||||
|
|
||||||
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
||||||
|
|
||||||
|
### json.exception.out_of_range.412
|
||||||
|
|
||||||
|
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
BSON length 2147483661 exceeds maximum of 2147483647
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, the length was silently truncated, and
|
||||||
|
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
|
||||||
|
[`from_bson`](../api/basic_json/from_bson.md) rejected.
|
||||||
|
|
||||||
|
### json.exception.out_of_range.413
|
||||||
|
|
||||||
|
A JSON Patch `remove` operation cannot be applied because the target location's parent is neither an object nor an array. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902), a `remove` target must reference a member of an existing object or an element of an existing array; a primitive value (string, number, boolean, etc.) or `null` has no members or elements to remove.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, this situation was silently ignored (the `remove` operation had no effect).
|
||||||
|
|
||||||
|
### json.exception.out_of_range.414
|
||||||
|
|
||||||
|
A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"path"` location. Per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) (section 4.4), a location cannot be moved into one of its own children.
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
cannot move value: 'from' path '/0' is a proper prefix of 'path' '/0/0'
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.
|
||||||
|
|
||||||
## Further exceptions
|
## Further exceptions
|
||||||
|
|
||||||
This exception is thrown in case of errors that cannot be classified with the
|
This exception is thrown in case of errors that cannot be classified with the
|
||||||
@@ -930,3 +1002,19 @@ A JSON Patch operation 'test' failed. The unsuccessful operation is also printed
|
|||||||
```
|
```
|
||||||
[json.exception.other_error.501] unsuccessful: {"op":"test","path":"/baz","value":"bar"}
|
[json.exception.other_error.501] unsuccessful: {"op":"test","path":"/baz","value":"bar"}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### json.exception.other_error.502
|
||||||
|
|
||||||
|
[`to_ubjson`](../api/basic_json/to_ubjson.md) and [`to_bjdata`](../api/basic_json/to_bjdata.md) were called with
|
||||||
|
`use_type = true` but `use_size = false`. UBJSON requires a size marker (`#`) after a type marker (`$`).
|
||||||
|
|
||||||
|
!!! failure "Example message"
|
||||||
|
|
||||||
|
```
|
||||||
|
[json.exception.other_error.502] use_type requires use_size = true
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
|
||||||
|
This exception was added in version 3.13.0. Before that, debug builds aborted on an assertion and release builds
|
||||||
|
wrote a `$` marker without `#`, which [`from_ubjson`](../api/basic_json/from_ubjson.md) then rejected.
|
||||||
|
|||||||
@@ -194,6 +194,27 @@ The library uses `std::numeric_limits<number_float_t>::digits10` (15 for IEEE `d
|
|||||||
|
|
||||||
See [this section](../features/types/number_handling.md#number-serialization) on the library's number handling for more information.
|
See [this section](../features/types/number_handling.md#number-serialization) on the library's number handling for more information.
|
||||||
|
|
||||||
|
### Serializing untrusted or invalid UTF-8
|
||||||
|
|
||||||
|
!!! question "Questions"
|
||||||
|
|
||||||
|
- Why does `dump()` throw when I serialize data that came from the network?
|
||||||
|
- Is CVE-2024-34363 a vulnerability in this library?
|
||||||
|
|
||||||
|
Crashes reported against this library that stem from an uncaught
|
||||||
|
[`type_error.316`](exceptions.md#jsonexceptiontype_error316) while serializing unvalidated input (e.g.,
|
||||||
|
CVE-2024-34363) are a usage issue, not a library vulnerability:
|
||||||
|
[`dump()`](../api/basic_json/dump.md) throws in its default `strict` mode because
|
||||||
|
[RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-8.1) requires JSON text to be valid UTF-8.
|
||||||
|
|
||||||
|
The recommended pattern is to pass a non-strict [`error_handler`](../api/basic_json/error_handler_t.md) or to handle the
|
||||||
|
exception:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// replace invalid sequences with U+FFFD instead of throwing
|
||||||
|
const auto s = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||||
|
```
|
||||||
|
|
||||||
### Using JSON values with `std::format` or `fmt`
|
### Using JSON values with `std::format` or `fmt`
|
||||||
|
|
||||||
!!! question
|
!!! question
|
||||||
|
|||||||
@@ -14,6 +14,5 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl
|
|||||||
- [Steve Sperandeo](https://github.com/homer6)
|
- [Steve Sperandeo](https://github.com/homer6)
|
||||||
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
||||||
- [Steve Wagner](https://github.com/ciroque)
|
- [Steve Wagner](https://github.com/ciroque)
|
||||||
- [Lion Yang](https://github.com/LionNatsu)
|
|
||||||
|
|
||||||
Thanks everyone!
|
Thanks everyone!
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1001 KiB |
@@ -135,6 +135,31 @@ Enable CI build targets. The exact targets are used during the several CI steps
|
|||||||
|
|
||||||
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
|
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
|
||||||
|
|
||||||
|
!!! warning "Does not apply to a pre-installed package"
|
||||||
|
|
||||||
|
This option only takes effect when building nlohmann/json from source as part of your own
|
||||||
|
CMake project (e.g. via [`FetchContent`](#fetchcontent) or [`add_subdirectory`](#external)).
|
||||||
|
It has **no effect** on a package that was already built and installed elsewhere (Homebrew,
|
||||||
|
vcpkg, a system package, etc.) — the resulting compile definition is baked into the exported
|
||||||
|
`nlohmann_jsonTargets.cmake` at install time, and `set(JSON_Diagnostics ON)` before
|
||||||
|
`find_package()` does not change it (verified against the Homebrew-installed package: the
|
||||||
|
exported target still carries a fixed `$<$<BOOL:OFF>:JSON_DIAGNOSTICS=1>`, regardless of any
|
||||||
|
variable set in the consuming project).
|
||||||
|
|
||||||
|
To enable extended diagnostics for a pre-installed package, override the imported target's
|
||||||
|
property directly after `find_package()`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
find_package(nlohmann_json REQUIRED)
|
||||||
|
set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
|
||||||
|
INTERFACE_COMPILE_DEFINITIONS "JSON_DIAGNOSTICS=1")
|
||||||
|
```
|
||||||
|
|
||||||
|
This only works cleanly when your project is the sole consumer of that imported target. If
|
||||||
|
nlohmann_json is pulled in from more than one place in your dependency graph with different
|
||||||
|
`JSON_DIAGNOSTICS` values, you may see a `"JSON_DIAGNOSTICS" redefined` compiler error, since
|
||||||
|
conflicting `-D` flags can end up on the same compile command line.
|
||||||
|
|
||||||
### `JSON_Diagnostic_Positions`
|
### `JSON_Diagnostic_Positions`
|
||||||
|
|
||||||
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default.
|
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default.
|
||||||
|
|||||||
@@ -930,6 +930,12 @@ If you are using [CocoaPods](https://cocoapods.org), you can use the library by
|
|||||||
to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues
|
to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues
|
||||||
[here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
|
[here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
|
||||||
|
|
||||||
|
## npm
|
||||||
|
|
||||||
|
This project does not publish an official [npm](https://www.npmjs.com) package. The npm package
|
||||||
|
[`nlohmann-json`](https://www.npmjs.com/package/nlohmann-json) (or similarly named packages) is not maintained or
|
||||||
|
endorsed by this project. Use one of the package managers listed above, or integrate the single header directly.
|
||||||
|
|
||||||
## ESP-IDF and PlatformIO
|
## ESP-IDF and PlatformIO
|
||||||
|
|
||||||
There is no official package published to the [ESP-IDF Component Registry](https://components.espressif.com) or the
|
There is no official package published to the [ESP-IDF Component Registry](https://components.espressif.com) or the
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ nav:
|
|||||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||||
|
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
||||||
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
||||||
@@ -308,6 +309,7 @@ nav:
|
|||||||
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md
|
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md
|
||||||
- Community:
|
- Community:
|
||||||
- community/index.md
|
- community/index.md
|
||||||
|
- community/ecosystem.md
|
||||||
- "Code of Conduct": community/code_of_conduct.md
|
- "Code of Conduct": community/code_of_conduct.md
|
||||||
- community/contribution_guidelines.md
|
- community/contribution_guidelines.md
|
||||||
- community/quality_assurance.md
|
- community/quality_assurance.md
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
wheel==0.47.0
|
wheel==0.48.0
|
||||||
|
|
||||||
mkdocs==1.6.1 # documentation framework
|
mkdocs==1.6.1 # documentation framework
|
||||||
mkdocs-git-revision-date-localized-plugin==1.5.3 # plugin "git-revision-date-localized"
|
mkdocs-git-revision-date-localized-plugin==1.5.4 # plugin "git-revision-date-localized"
|
||||||
mkdocs-material==9.7.6 # theme for mkdocs
|
mkdocs-material==9.7.7 # theme for mkdocs
|
||||||
mkdocs-material-extensions==1.3.1 # extensions
|
mkdocs-material-extensions==1.3.1 # extensions
|
||||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <unordered_map> // unordered_map
|
#include <unordered_map> // unordered_map
|
||||||
#include <utility> // pair, declval
|
#include <utility> // pair, declval
|
||||||
#include <valarray> // valarray
|
#include <valarray> // valarray
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
@@ -332,6 +333,7 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
|
|||||||
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
|
||||||
!is_basic_json<ConstructibleArrayType>::value,
|
!is_basic_json<ConstructibleArrayType>::value,
|
||||||
int > = 0 >
|
int > = 0 >
|
||||||
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
|
||||||
@@ -377,6 +379,36 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
|
|||||||
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
if (j.is_binary())
|
||||||
|
{
|
||||||
|
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
|
||||||
|
}
|
||||||
|
else if (j.is_array())
|
||||||
|
{
|
||||||
|
from_json_array_impl(j, bin, priority_tag<3> {});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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*/)
|
||||||
|
{}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||||
@@ -388,6 +420,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> {});
|
||||||
for (const auto& p : *inner_object)
|
for (const auto& p : *inner_object)
|
||||||
{
|
{
|
||||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||||
|
|||||||
@@ -1075,8 +1075,8 @@ char* to_chars(char* first, const char* last, FloatType value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
if (value == 0) // +-0
|
if (value == 0) // +-0
|
||||||
{
|
{
|
||||||
@@ -1087,7 +1087,7 @@ char* to_chars(char* first, const char* last, FloatType value)
|
|||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);
|
||||||
|
|||||||
@@ -377,6 +377,7 @@ template < typename BasicJsonType, typename CompatibleArrayType,
|
|||||||
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
|
||||||
|
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
|
||||||
!is_basic_json<CompatibleArrayType>::value
|
!is_basic_json<CompatibleArrayType>::value
|
||||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||||
&& !is_compatible_range_view<CompatibleArrayType>::value
|
&& !is_compatible_range_view<CompatibleArrayType>::value
|
||||||
@@ -406,6 +407,14 @@ inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bi
|
|||||||
external_constructor<value_t::binary>::construct(j, bin);
|
external_constructor<value_t::binary>::construct(j, bin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template < typename BasicJsonType, typename CompatibleArrayType,
|
||||||
|
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
|
||||||
|
int > = 0 >
|
||||||
|
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
|
||||||
|
{
|
||||||
|
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
|
||||||
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, typename T,
|
template<typename BasicJsonType, typename T,
|
||||||
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
|
||||||
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
|
||||||
|
|||||||
@@ -33,8 +33,8 @@
|
|||||||
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087
|
// code stumbling over this. See https://github.com/nlohmann/json/issues/4087
|
||||||
// for a discussion.
|
// for a discussion.
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma clang diagnostic ignored "-Wweak-vtables"
|
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wweak-vtables")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -287,5 +287,5 @@ class other_error : public exception
|
|||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -155,9 +155,31 @@ class input_stream_adapter
|
|||||||
|
|
||||||
// General-purpose iterator-based adapter. It might not be as fast as
|
// General-purpose iterator-based adapter. It might not be as fast as
|
||||||
// theoretically possible for some containers, but it is extremely versatile.
|
// theoretically possible for some containers, but it is extremely versatile.
|
||||||
template<typename IteratorType>
|
// SentinelType defaults to IteratorType for backward compatibility, but may be
|
||||||
|
// a different type, e.g. a C++20 sentinel such as std::default_sentinel_t when
|
||||||
|
// IteratorType is a std::counted_iterator.
|
||||||
|
template<typename IteratorType, typename SentinelType = IteratorType>
|
||||||
class iterator_input_adapter
|
class iterator_input_adapter
|
||||||
{
|
{
|
||||||
|
// Whether the number of elements between two positions can be computed in
|
||||||
|
// O(1): either the iterator and the sentinel have the same type (plain
|
||||||
|
// std::distance) or, in C++20, the sentinel is a sized sentinel for the
|
||||||
|
// iterator (std::ranges::distance), e.g. std::default_sentinel_t paired
|
||||||
|
// with std::counted_iterator.
|
||||||
|
//
|
||||||
|
// JSON_HAS_RANGES gates the C++20 branch: on standard libraries with an
|
||||||
|
// incomplete <ranges> (libstdc++ < 11, see #4440) evaluating
|
||||||
|
// std::contiguous_iterator on a std::counted_iterator is a hard error
|
||||||
|
// instead of yielding false, and these traits are instantiated for every
|
||||||
|
// adapter. Such toolchains fall back to the pointer-only test and simply
|
||||||
|
// use the byte-at-a-time scanner.
|
||||||
|
static constexpr bool sentinel_is_sized =
|
||||||
|
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||||
|
std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>;
|
||||||
|
#else
|
||||||
|
std::is_same<IteratorType, SentinelType>::value;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
using char_type = typename std::iterator_traits<IteratorType>::value_type;
|
||||||
|
|
||||||
@@ -169,9 +191,10 @@ class iterator_input_adapter
|
|||||||
// in wide_string_input_adapter, which does not expose this).
|
// in wide_string_input_adapter, which does not expose this).
|
||||||
static constexpr bool supports_seek =
|
static constexpr bool supports_seek =
|
||||||
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
|
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
|
||||||
|
&& sentinel_is_sized
|
||||||
&& sizeof(char_type) == 1;
|
&& sizeof(char_type) == 1;
|
||||||
|
|
||||||
iterator_input_adapter(IteratorType first, IteratorType last)
|
iterator_input_adapter(IteratorType first, SentinelType last)
|
||||||
: begin(first), current(std::move(first)), end(std::move(last))
|
: begin(first), current(std::move(first)), end(std::move(last))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -216,19 +239,60 @@ class iterator_input_adapter
|
|||||||
private:
|
private:
|
||||||
// whether IteratorType refers to a contiguous range and therefore supports
|
// whether IteratorType refers to a contiguous range and therefore supports
|
||||||
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
|
||||||
// library iterators such as those of std::vector and std::string)
|
// library iterators such as those of std::vector and std::string). The
|
||||||
static constexpr bool iterator_is_contiguous =
|
// available element count must also be computable in O(1), hence
|
||||||
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
// sentinel_is_sized.
|
||||||
std::contiguous_iterator<IteratorType> ||
|
static constexpr bool iterator_is_contiguous = sentinel_is_sized &&
|
||||||
#endif
|
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||||
|
(std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
|
||||||
|
#else
|
||||||
std::is_pointer<IteratorType>::value;
|
std::is_pointer<IteratorType>::value;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// number of unread elements in [current, end)
|
||||||
|
std::size_t remaining_count() const
|
||||||
|
{
|
||||||
|
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
|
||||||
|
// std::ranges::distance also supports sized sentinels of a different
|
||||||
|
// type (e.g. std::counted_iterator + std::default_sentinel_t)
|
||||||
|
return static_cast<std::size_t>(std::ranges::distance(current, end));
|
||||||
|
#else
|
||||||
|
return static_cast<std::size_t>(std::distance(current, end));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Whether the remaining input is a single contiguous block of 1-byte
|
||||||
|
// elements that the lexer can inspect directly (used for the SWAR string
|
||||||
|
// fast path).
|
||||||
|
static constexpr bool supports_bulk_scan =
|
||||||
|
iterator_is_contiguous && sizeof(char_type) == 1;
|
||||||
|
|
||||||
|
// Pointer to the next unread element; only valid when bulk_remaining() > 0.
|
||||||
|
const char_type* bulk_data() const
|
||||||
|
{
|
||||||
|
return &*current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of unread elements available as one contiguous block.
|
||||||
|
std::size_t bulk_remaining() const
|
||||||
|
{
|
||||||
|
return remaining_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume @a n elements previously inspected via bulk_data().
|
||||||
|
void bulk_skip(std::size_t n)
|
||||||
|
{
|
||||||
|
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
||||||
{
|
{
|
||||||
const std::size_t wanted = count * sizeof(T);
|
const std::size_t wanted = count * sizeof(T);
|
||||||
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
|
const std::size_t available = remaining_count() * sizeof(char_type);
|
||||||
const std::size_t copied = (std::min)(wanted, available);
|
const std::size_t copied = (std::min)(wanted, available);
|
||||||
if (JSON_HEDLEY_LIKELY(copied != 0))
|
if (JSON_HEDLEY_LIKELY(copied != 0))
|
||||||
{
|
{
|
||||||
@@ -267,7 +331,7 @@ class iterator_input_adapter
|
|||||||
|
|
||||||
IteratorType begin;
|
IteratorType begin;
|
||||||
IteratorType current;
|
IteratorType current;
|
||||||
IteratorType end;
|
SentinelType end;
|
||||||
|
|
||||||
template<typename BaseInputAdapter, size_t T>
|
template<typename BaseInputAdapter, size_t T>
|
||||||
friend struct wide_string_input_helper;
|
friend struct wide_string_input_helper;
|
||||||
@@ -331,8 +395,12 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// unknown character
|
// A code point above U+10FFFF has no UTF-8 encoding. Passing the
|
||||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
// unit through would narrow it to int, where 0xFFFFFFFF becomes
|
||||||
|
// char_traits<char>::eof() and would end the input silently, so
|
||||||
|
// emit a byte that is never valid UTF-8 and let the decoder
|
||||||
|
// reject it.
|
||||||
|
utf8_bytes[0] = 0xFF;
|
||||||
utf8_bytes_filled = 1;
|
utf8_bytes_filled = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,17 +449,30 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (JSON_HEDLEY_UNLIKELY(!input.empty()))
|
// A supplementary code point is a high surrogate (0xD800..0xDBFF)
|
||||||
|
// followed by a low surrogate (0xDC00..0xDFFF). A lone low
|
||||||
|
// surrogate, a high surrogate at the end of the input, or a high
|
||||||
|
// surrogate followed by any other unit is malformed UTF-16. In
|
||||||
|
// that case the offending unit is passed through unchanged so the
|
||||||
|
// UTF-8 decoder rejects it, matching how \uXXXX surrogate escapes
|
||||||
|
// are handled in the lexer.
|
||||||
|
bool valid_pair = false;
|
||||||
|
if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty()))
|
||||||
{
|
{
|
||||||
const auto wc2 = static_cast<unsigned int>(input.get_character());
|
const auto wc2 = static_cast<unsigned int>(input.get_character());
|
||||||
|
if (0xDC00 <= wc2 && wc2 <= 0xDFFF)
|
||||||
|
{
|
||||||
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
|
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
|
||||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
|
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
|
||||||
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
|
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
|
||||||
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
|
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
|
||||||
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
|
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
|
||||||
utf8_bytes_filled = 4;
|
utf8_bytes_filled = 4;
|
||||||
|
valid_pair = true;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
if (!valid_pair)
|
||||||
{
|
{
|
||||||
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
|
||||||
utf8_bytes_filled = 1;
|
utf8_bytes_filled = 1;
|
||||||
@@ -453,19 +534,54 @@ class wide_string_input_adapter
|
|||||||
std::size_t utf8_bytes_filled = 0;
|
std::size_t utf8_bytes_filled = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename IteratorType, typename Enable = void>
|
template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void>
|
||||||
struct iterator_input_adapter_factory
|
struct iterator_input_adapter_factory
|
||||||
{
|
{
|
||||||
using iterator_type = IteratorType;
|
using iterator_type = IteratorType;
|
||||||
|
using sentinel_type = SentinelType;
|
||||||
using char_type = typename std::iterator_traits<iterator_type>::value_type;
|
using char_type = typename std::iterator_traits<iterator_type>::value_type;
|
||||||
using adapter_type = iterator_input_adapter<iterator_type>;
|
using adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
|
||||||
|
|
||||||
static adapter_type create(IteratorType first, IteratorType last)
|
static adapter_type create(IteratorType first, SentinelType last)
|
||||||
{
|
{
|
||||||
return adapter_type(std::move(first), std::move(last));
|
return adapter_type(std::move(first), std::move(last));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Detection: whether IteratorType and SentinelType can be compared with !=
|
||||||
|
template<typename IteratorType, typename SentinelType, typename = void>
|
||||||
|
struct can_compare_ne_impl : std::false_type {};
|
||||||
|
|
||||||
|
template<typename IteratorType, typename SentinelType>
|
||||||
|
struct can_compare_ne_impl < IteratorType, SentinelType,
|
||||||
|
void_t < decltype(std::declval<IteratorType>() != std::declval<SentinelType>()) >>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
// Workaround for reversed operator order
|
||||||
|
template<typename IteratorType, typename SentinelType, typename = void>
|
||||||
|
struct can_compare_ne_reversed : std::false_type {};
|
||||||
|
|
||||||
|
template<typename IteratorType, typename SentinelType>
|
||||||
|
struct can_compare_ne_reversed < IteratorType, SentinelType,
|
||||||
|
void_t < decltype(std::declval<SentinelType>() != std::declval<IteratorType>()) >>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template<typename IteratorType, typename SentinelType>
|
||||||
|
struct can_compare_ne_either_order : std::integral_constant < bool,
|
||||||
|
can_compare_ne_impl<IteratorType, SentinelType>::value ||
|
||||||
|
can_compare_ne_reversed<IteratorType, SentinelType>::value > {};
|
||||||
|
|
||||||
|
// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a
|
||||||
|
// trailing default argument (e.g. parse(s, nullptr, ...)) must never be
|
||||||
|
// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably
|
||||||
|
// SFINAE the `operator!=` detection above for std::nullptr_t against
|
||||||
|
// container/string types, which would otherwise make such calls ambiguous
|
||||||
|
// with the compatible-input overload.
|
||||||
|
template<typename IteratorType, typename SentinelType>
|
||||||
|
struct can_compare_ne : std::integral_constant < bool,
|
||||||
|
!std::is_same<SentinelType, std::nullptr_t>::value &&
|
||||||
|
can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct is_iterator_of_multibyte
|
struct is_iterator_of_multibyte
|
||||||
{
|
{
|
||||||
@@ -476,28 +592,74 @@ struct is_iterator_of_multibyte
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType>
|
||||||
struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
|
struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
|
||||||
{
|
{
|
||||||
using iterator_type = IteratorType;
|
using iterator_type = IteratorType;
|
||||||
|
using sentinel_type = SentinelType;
|
||||||
using char_type = typename std::iterator_traits<iterator_type>::value_type;
|
using char_type = typename std::iterator_traits<iterator_type>::value_type;
|
||||||
using base_adapter_type = iterator_input_adapter<iterator_type>;
|
using base_adapter_type = iterator_input_adapter<iterator_type, sentinel_type>;
|
||||||
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
|
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
|
||||||
|
|
||||||
static adapter_type create(IteratorType first, IteratorType last)
|
static adapter_type create(IteratorType first, SentinelType last)
|
||||||
{
|
{
|
||||||
return adapter_type(base_adapter_type(std::move(first), std::move(last)));
|
return adapter_type(base_adapter_type(std::move(first), std::move(last)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// General purpose iterator-based input
|
// General purpose iterator-based input (iterator+sentinel pair; SentinelType
|
||||||
template<typename IteratorType>
|
// defaults to IteratorType for the common same-type case, but may differ for
|
||||||
typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
|
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
|
||||||
|
// be compared with !=.
|
||||||
|
template < typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
typename = typename std::enable_if <
|
||||||
|
can_compare_ne<IteratorType, SentinelType>::value >::type >
|
||||||
|
typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_type input_adapter(IteratorType first, SentinelType last)
|
||||||
{
|
{
|
||||||
using factory_type = iterator_input_adapter_factory<IteratorType>;
|
using factory_type = iterator_input_adapter_factory<IteratorType, SentinelType>;
|
||||||
return factory_type::create(first, last);
|
return factory_type::create(first, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The element type a container's data() points at, cv-qualifiers removed.
|
||||||
|
// Ill-formed - and therefore SFINAE-friendly - for types without data().
|
||||||
|
template<typename ContainerType>
|
||||||
|
using container_data_t = typename std::remove_cv<typename std::remove_pointer <
|
||||||
|
decltype(std::declval<const ContainerType&>().data()) >::type >::type;
|
||||||
|
|
||||||
|
// The container's own element type, cv-qualifiers removed. It is looked up on
|
||||||
|
// the bare type so it is also found when ContainerType is deduced as a
|
||||||
|
// reference by the forwarding-reference overload below.
|
||||||
|
template<typename ContainerType>
|
||||||
|
using container_value_t = typename std::remove_cv <
|
||||||
|
typename std::remove_cv<typename std::remove_reference<ContainerType>::type>::type::value_type >::type;
|
||||||
|
|
||||||
|
// Detect a container that stores its elements contiguously as single bytes
|
||||||
|
// (std::string, std::vector<char/unsigned char>, std::array<char, N>,
|
||||||
|
// std::string_view, ...). Such inputs are wrapped in a pointer-based adapter so
|
||||||
|
// they benefit from the contiguous fast paths (bulk string scanning, memcpy for
|
||||||
|
// binary formats) in every C++ standard - not only in C++20, where the standard
|
||||||
|
// library iterators model std::contiguous_iterator and are detected directly.
|
||||||
|
//
|
||||||
|
// data() and size() on their own would be duck typing: they say nothing about
|
||||||
|
// size() counting the units data() points at, and reading [data(), data() +
|
||||||
|
// size()) as bytes would be wrong for a type where it does not. Requiring the
|
||||||
|
// container's own value_type to be that same single-byte element ties the two
|
||||||
|
// together; every contiguous standard container satisfies it. Anything else
|
||||||
|
// keeps the iterator-based adapter, which is always correct - only slower.
|
||||||
|
template<typename ContainerType, typename = void>
|
||||||
|
struct is_contiguous_byte_container : std::false_type {};
|
||||||
|
|
||||||
|
template<typename ContainerType>
|
||||||
|
struct is_contiguous_byte_container < ContainerType, void_t <
|
||||||
|
container_data_t<ContainerType>,
|
||||||
|
container_value_t<ContainerType>,
|
||||||
|
decltype(std::declval<const ContainerType&>().size()) >>
|
||||||
|
: std::integral_constant < bool,
|
||||||
|
std::is_pointer<decltype(std::declval<const ContainerType&>().data())>::value&&
|
||||||
|
std::is_integral<container_data_t<ContainerType>>::value&&
|
||||||
|
sizeof(container_data_t<ContainerType>) == 1 &&
|
||||||
|
std::is_same<container_data_t<ContainerType>, container_value_t<ContainerType>>::value > {};
|
||||||
|
|
||||||
// Convenience shorthand from container to iterator
|
// Convenience shorthand from container to iterator
|
||||||
// Enables ADL on begin(container) and end(container)
|
// Enables ADL on begin(container) and end(container)
|
||||||
// Encloses the using declarations in namespace for not to leak them to outside scope
|
// Encloses the using declarations in namespace for not to leak them to outside scope
|
||||||
@@ -525,12 +687,32 @@ struct container_input_adapter_factory< ContainerType,
|
|||||||
|
|
||||||
} // namespace container_input_adapter_factory_impl
|
} // namespace container_input_adapter_factory_impl
|
||||||
|
|
||||||
template<typename ContainerType>
|
// General container path (iterator-based). Contiguous single-byte containers
|
||||||
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(ContainerType&& container)
|
// are excluded here and routed through the pointer-based overload below.
|
||||||
|
template < typename ContainerType,
|
||||||
|
enable_if_t < !is_contiguous_byte_container<ContainerType>::value, int > = 0 >
|
||||||
|
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(ContainerType && container)
|
||||||
{
|
{
|
||||||
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
|
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contiguous single-byte containers (std::string, std::vector<char>, ...) are
|
||||||
|
// wrapped in a pointer-based adapter so the contiguous fast paths apply in every
|
||||||
|
// standard. The pointer keeps the container's own element type (const char* for
|
||||||
|
// std::string, const std::uint8_t* for std::vector<std::uint8_t>, ...), so the
|
||||||
|
// resulting char_type - and therefore the parsing behavior - is byte-for-byte
|
||||||
|
// identical to the iterator-based path; only the raw pointer additionally
|
||||||
|
// enables the bulk fast paths. The container outlives the adapter for the whole
|
||||||
|
// parse (temporaries live until the end of the full expression), exactly as the
|
||||||
|
// iterators it replaces did.
|
||||||
|
template < typename ContainerType,
|
||||||
|
enable_if_t < is_contiguous_byte_container<ContainerType>::value, int > = 0 >
|
||||||
|
auto input_adapter(const ContainerType& container)
|
||||||
|
-> decltype(input_adapter(container.data(), container.data() + container.size()))
|
||||||
|
{
|
||||||
|
return input_adapter(container.data(), container.data() + container.size());
|
||||||
|
}
|
||||||
|
|
||||||
// specialization for std::string
|
// specialization for std::string
|
||||||
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
|
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm> // find_if
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
#include <type_traits> // enable_if_t
|
#include <type_traits> // enable_if_t
|
||||||
#include <utility> // move
|
#include <utility> // move, pair
|
||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
@@ -222,12 +223,16 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
bool string(string_t& val)
|
bool string(string_t& val)
|
||||||
{
|
{
|
||||||
handle_value(val);
|
// json_sax documents that the passed value may be moved from,
|
||||||
|
// so hand the buffer over instead of copying it
|
||||||
|
handle_value(std::move(val));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool binary(binary_t& val)
|
bool binary(binary_t& val)
|
||||||
{
|
{
|
||||||
|
// json_sax documents that the passed value may be moved from,
|
||||||
|
// so hand the buffer over instead of copying it
|
||||||
handle_value(std::move(val));
|
handle_value(std::move(val));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -249,7 +254,7 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||||
{
|
{
|
||||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -298,7 +303,7 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||||
{
|
{
|
||||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -370,8 +375,10 @@ class json_sax_dom_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// include the length of the quotes, which is 2
|
// escape sequences make the token longer than the value it
|
||||||
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
// parses to, so the start position cannot be derived from
|
||||||
|
// the value; use the offset the lexer recorded instead
|
||||||
|
v.start_position = m_lexer_ref->get_token_start_position();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,12 +537,16 @@ class json_sax_dom_callback_parser
|
|||||||
|
|
||||||
bool string(string_t& val)
|
bool string(string_t& val)
|
||||||
{
|
{
|
||||||
handle_value(val);
|
// json_sax documents that the passed value may be moved from,
|
||||||
|
// so hand the buffer over instead of copying it
|
||||||
|
handle_value(std::move(val));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool binary(binary_t& val)
|
bool binary(binary_t& val)
|
||||||
{
|
{
|
||||||
|
// json_sax documents that the passed value may be moved from,
|
||||||
|
// so hand the buffer over instead of copying it
|
||||||
handle_value(std::move(val));
|
handle_value(std::move(val));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -546,6 +557,11 @@ class json_sax_dom_callback_parser
|
|||||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);
|
||||||
keep_stack.push_back(keep);
|
keep_stack.push_back(keep);
|
||||||
|
|
||||||
|
// the key this object will be stored under, read before handle_value()
|
||||||
|
// may consume it; kept in lockstep with ref_stack so end_object() can
|
||||||
|
// find the object in its parent again
|
||||||
|
container_key_stack.push_back(current_key());
|
||||||
|
|
||||||
auto val = handle_value(BasicJsonType::value_t::object, true);
|
auto val = handle_value(BasicJsonType::value_t::object, true);
|
||||||
ref_stack.push_back(val.second);
|
ref_stack.push_back(val.second);
|
||||||
|
|
||||||
@@ -566,7 +582,7 @@ class json_sax_dom_callback_parser
|
|||||||
// check object limit
|
// check object limit
|
||||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||||
{
|
{
|
||||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -579,11 +595,24 @@ class json_sax_dom_callback_parser
|
|||||||
// check callback for the key
|
// check callback for the key
|
||||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
|
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);
|
||||||
key_keep_stack.push_back(keep);
|
key_keep_stack.push_back(keep);
|
||||||
|
// remember the key so a rejected value can be erased without searching
|
||||||
|
// the object for it (kept in lockstep with key_keep_stack)
|
||||||
|
key_stack.push_back(val);
|
||||||
|
|
||||||
// add discarded value at the given key and store the reference for later
|
// add discarded value at the given key and store the reference for later
|
||||||
if (keep && ref_stack.back())
|
if (keep && ref_stack.back())
|
||||||
{
|
{
|
||||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
|
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||||
|
const auto it = obj.find(val);
|
||||||
|
if (it != obj.end())
|
||||||
|
{
|
||||||
|
// this is a duplicate key (legal in JSON); remember its
|
||||||
|
// current value so it can be restored later if the new
|
||||||
|
// value is rejected by the callback, instead of being
|
||||||
|
// erased together with the discarded placeholder
|
||||||
|
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||||
|
}
|
||||||
|
object_element = &(obj[val] = discarded);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -595,7 +624,11 @@ class json_sax_dom_callback_parser
|
|||||||
{
|
{
|
||||||
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
|
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
|
||||||
{
|
{
|
||||||
// discard object
|
// discard object, unless this slot holds a duplicate key's
|
||||||
|
// previous value pending restoration, in which case that
|
||||||
|
// value is restored instead of being discarded
|
||||||
|
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||||
|
{
|
||||||
*ref_stack.back() = discarded;
|
*ref_stack.back() = discarded;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -603,6 +636,7 @@ class json_sax_dom_callback_parser
|
|||||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -615,25 +649,25 @@ class json_sax_dom_callback_parser
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
ref_stack.back()->set_parents();
|
ref_stack.back()->set_parents();
|
||||||
|
// this object is finally, definitively kept; drop any
|
||||||
|
// pending duplicate-key stash entry for its slot since it
|
||||||
|
// can no longer be restored
|
||||||
|
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_ASSERT(!ref_stack.empty());
|
JSON_ASSERT(!ref_stack.empty());
|
||||||
JSON_ASSERT(!keep_stack.empty());
|
JSON_ASSERT(!keep_stack.empty());
|
||||||
|
JSON_ASSERT(!container_key_stack.empty());
|
||||||
ref_stack.pop_back();
|
ref_stack.pop_back();
|
||||||
keep_stack.pop_back();
|
keep_stack.pop_back();
|
||||||
|
const string_t object_key = std::move(container_key_stack.back());
|
||||||
|
container_key_stack.pop_back();
|
||||||
|
|
||||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||||
{
|
{
|
||||||
// remove discarded value
|
// remove discarded value
|
||||||
for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
|
remove_discarded_value(*ref_stack.back(), object_key);
|
||||||
{
|
|
||||||
if (it->is_discarded())
|
|
||||||
{
|
|
||||||
ref_stack.back()->erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -644,6 +678,9 @@ class json_sax_dom_callback_parser
|
|||||||
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);
|
||||||
keep_stack.push_back(keep);
|
keep_stack.push_back(keep);
|
||||||
|
|
||||||
|
// see start_object()
|
||||||
|
container_key_stack.push_back(current_key());
|
||||||
|
|
||||||
auto val = handle_value(BasicJsonType::value_t::array, true);
|
auto val = handle_value(BasicJsonType::value_t::array, true);
|
||||||
ref_stack.push_back(val.second);
|
ref_stack.push_back(val.second);
|
||||||
|
|
||||||
@@ -664,7 +701,7 @@ class json_sax_dom_callback_parser
|
|||||||
// check array limit
|
// check array limit
|
||||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||||
{
|
{
|
||||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,8 +711,9 @@ class json_sax_dom_callback_parser
|
|||||||
bool end_array()
|
bool end_array()
|
||||||
{
|
{
|
||||||
bool keep = true;
|
bool keep = true;
|
||||||
|
const bool stored = ref_stack.back() != nullptr;
|
||||||
|
|
||||||
if (ref_stack.back())
|
if (stored)
|
||||||
{
|
{
|
||||||
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
|
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
|
||||||
if (keep)
|
if (keep)
|
||||||
@@ -690,10 +728,18 @@ class json_sax_dom_callback_parser
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
ref_stack.back()->set_parents();
|
ref_stack.back()->set_parents();
|
||||||
|
// this array is finally, definitively kept; drop any
|
||||||
|
// pending duplicate-key stash entry for its slot since it
|
||||||
|
// can no longer be restored
|
||||||
|
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// discard array
|
// discard array, unless this slot holds a duplicate key's
|
||||||
|
// previous value pending restoration, in which case that
|
||||||
|
// value is restored instead of being discarded
|
||||||
|
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||||
|
{
|
||||||
*ref_stack.back() = discarded;
|
*ref_stack.back() = discarded;
|
||||||
|
|
||||||
#if JSON_DIAGNOSTIC_POSITIONS
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
@@ -702,17 +748,31 @@ class json_sax_dom_callback_parser
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JSON_ASSERT(!ref_stack.empty());
|
JSON_ASSERT(!ref_stack.empty());
|
||||||
JSON_ASSERT(!keep_stack.empty());
|
JSON_ASSERT(!keep_stack.empty());
|
||||||
|
JSON_ASSERT(!container_key_stack.empty());
|
||||||
ref_stack.pop_back();
|
ref_stack.pop_back();
|
||||||
keep_stack.pop_back();
|
keep_stack.pop_back();
|
||||||
|
const string_t object_key = std::move(container_key_stack.back());
|
||||||
|
container_key_stack.pop_back();
|
||||||
|
|
||||||
// remove discarded value
|
// remove discarded value
|
||||||
if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
|
if (!ref_stack.empty() && ref_stack.back())
|
||||||
|
{
|
||||||
|
if (!keep && ref_stack.back()->is_array())
|
||||||
{
|
{
|
||||||
ref_stack.back()->m_data.m_value.array->pop_back();
|
ref_stack.back()->m_data.m_value.array->pop_back();
|
||||||
}
|
}
|
||||||
|
else if ((!keep || !stored) && ref_stack.back()->is_object())
|
||||||
|
{
|
||||||
|
// the array is either still stored under its key or was never
|
||||||
|
// stored, leaving the placeholder key() wrote; both show up as
|
||||||
|
// a discarded member of the parent object
|
||||||
|
remove_discarded_value(*ref_stack.back(), object_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -765,8 +825,10 @@ class json_sax_dom_callback_parser
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// include the length of the quotes, which is 2
|
// escape sequences make the token longer than the value it
|
||||||
v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
|
// parses to, so the start position cannot be derived from
|
||||||
|
// the value; use the offset the lexer recorded instead
|
||||||
|
v.start_position = m_lexer_ref->get_token_start_position();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,6 +863,94 @@ class json_sax_dom_callback_parser
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief the key the value now being handled will be stored under
|
||||||
|
|
||||||
|
Empty unless the enclosing container is an object, in which case it is the
|
||||||
|
key of the pending key() event. Read before handle_value() consumes that
|
||||||
|
key, so it is also correct when the value never reaches its parent.
|
||||||
|
*/
|
||||||
|
string_t current_key() const
|
||||||
|
{
|
||||||
|
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
||||||
|
&& !key_stack.empty())
|
||||||
|
{
|
||||||
|
return key_stack.back();
|
||||||
|
}
|
||||||
|
return string_t{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// if there is a pending duplicate-key stash entry for this exact slot,
|
||||||
|
/// remove it from the stash; if restore_value is true, the stashed
|
||||||
|
/// previous value is moved back into the slot first (use this when the
|
||||||
|
/// new value at that slot was rejected); otherwise the stash entry is
|
||||||
|
/// simply dropped (use this when the new value was accepted, so it
|
||||||
|
/// correctly supersedes the old one and no restore should ever happen
|
||||||
|
/// for this slot again)
|
||||||
|
/// @return whether a matching stash entry was found (and processed)
|
||||||
|
bool resolve_duplicate_key_stash(BasicJsonType* slot, bool restore_value)
|
||||||
|
{
|
||||||
|
const auto it = std::find_if(duplicate_key_stash.begin(), duplicate_key_stash.end(),
|
||||||
|
[slot](const std::pair<BasicJsonType*, BasicJsonType>& entry)
|
||||||
|
{
|
||||||
|
return entry.first == slot;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it == duplicate_key_stash.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restore_value)
|
||||||
|
{
|
||||||
|
*slot = std::move(it->second);
|
||||||
|
}
|
||||||
|
duplicate_key_stash.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief remove the discarded value the callback rejected from its parent
|
||||||
|
|
||||||
|
A rejected value can only ever be the one most recently added to @a parent:
|
||||||
|
the last element of an array, or the placeholder key() stored under @a key
|
||||||
|
in an object. Looking there directly makes this O(1) resp. O(log n), where
|
||||||
|
searching @a parent for it made a filtering parse quadratic in the number of
|
||||||
|
members of a single container.
|
||||||
|
|
||||||
|
Finding no discarded value there means none was stored in the first place -
|
||||||
|
the callback rejected the value before it reached its parent - so there is
|
||||||
|
nothing to remove. If the discarded slot instead holds a duplicate key's
|
||||||
|
stashed previous value pending restoration, that value is restored instead
|
||||||
|
of the slot being erased.
|
||||||
|
|
||||||
|
@param[in,out] parent the container to remove the rejected value from
|
||||||
|
@param[in] key the key the value was stored under; unused for arrays
|
||||||
|
*/
|
||||||
|
void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||||
|
{
|
||||||
|
if (parent.is_array())
|
||||||
|
{
|
||||||
|
auto& array = *parent.m_data.m_value.array;
|
||||||
|
if (!array.empty() && array.back().is_discarded())
|
||||||
|
{
|
||||||
|
array.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (parent.is_object())
|
||||||
|
{
|
||||||
|
auto& object = *parent.m_data.m_value.object;
|
||||||
|
const auto it = object.find(key);
|
||||||
|
if (it != object.end() && it->second.is_discarded())
|
||||||
|
{
|
||||||
|
if (!resolve_duplicate_key_stash(&(it->second), true))
|
||||||
|
{
|
||||||
|
object.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] v value to add to the JSON value we build during parsing
|
@param[in] v value to add to the JSON value we build during parsing
|
||||||
@param[in] skip_callback whether we should skip calling the callback
|
@param[in] skip_callback whether we should skip calling the callback
|
||||||
@@ -841,6 +991,21 @@ class json_sax_dom_callback_parser
|
|||||||
// do not handle this value if we just learnt it shall be discarded
|
// do not handle this value if we just learnt it shall be discarded
|
||||||
if (!keep)
|
if (!keep)
|
||||||
{
|
{
|
||||||
|
// if the value was to become an object member, key() already
|
||||||
|
// stored a placeholder for it that has to be removed again
|
||||||
|
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!key_keep_stack.empty());
|
||||||
|
JSON_ASSERT(!key_stack.empty());
|
||||||
|
const bool placeholder_stored = key_keep_stack.back();
|
||||||
|
key_keep_stack.pop_back();
|
||||||
|
const string_t key = std::move(key_stack.back());
|
||||||
|
key_stack.pop_back();
|
||||||
|
if (placeholder_stored)
|
||||||
|
{
|
||||||
|
remove_discarded_value(*ref_stack.back(), key);
|
||||||
|
}
|
||||||
|
}
|
||||||
return {false, nullptr};
|
return {false, nullptr};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -871,8 +1036,10 @@ class json_sax_dom_callback_parser
|
|||||||
JSON_ASSERT(ref_stack.back()->is_object());
|
JSON_ASSERT(ref_stack.back()->is_object());
|
||||||
// check if we should store an element for the current key
|
// check if we should store an element for the current key
|
||||||
JSON_ASSERT(!key_keep_stack.empty());
|
JSON_ASSERT(!key_keep_stack.empty());
|
||||||
|
JSON_ASSERT(!key_stack.empty());
|
||||||
const bool store_element = key_keep_stack.back();
|
const bool store_element = key_keep_stack.back();
|
||||||
key_keep_stack.pop_back();
|
key_keep_stack.pop_back();
|
||||||
|
key_stack.pop_back();
|
||||||
|
|
||||||
if (!store_element)
|
if (!store_element)
|
||||||
{
|
{
|
||||||
@@ -881,6 +1048,16 @@ class json_sax_dom_callback_parser
|
|||||||
|
|
||||||
JSON_ASSERT(object_element);
|
JSON_ASSERT(object_element);
|
||||||
*object_element = std::move(value);
|
*object_element = std::move(value);
|
||||||
|
if (!skip_callback)
|
||||||
|
{
|
||||||
|
// this scalar value finally, definitively replaces whatever was
|
||||||
|
// at this slot; drop any pending duplicate-key stash entry for
|
||||||
|
// it since it can no longer be restored (a container value at
|
||||||
|
// this slot is resolved later, in end_object()/end_array(),
|
||||||
|
// since skip_callback is true for the placeholder handling that
|
||||||
|
// happens here for those)
|
||||||
|
resolve_duplicate_key_stash(object_element, false);
|
||||||
|
}
|
||||||
return {true, object_element};
|
return {true, object_element};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -892,8 +1069,20 @@ class json_sax_dom_callback_parser
|
|||||||
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
|
std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
/// stack to manage which object keys to keep
|
/// stack to manage which object keys to keep
|
||||||
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
|
std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// the keys key() stored a placeholder for, in lockstep with key_keep_stack
|
||||||
|
std::vector<string_t> key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
|
/// for each open container, the key it is stored under in its parent
|
||||||
|
/// object, in lockstep with ref_stack; unused where the parent is not an
|
||||||
|
/// object
|
||||||
|
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||||
/// 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;
|
||||||
|
/// stash of (slot pointer, previous value) for object members that
|
||||||
|
/// already existed when key() was called again for the same key
|
||||||
|
/// (duplicate keys); used to restore the previous value if the new
|
||||||
|
/// value is later rejected by the callback, instead of erasing the
|
||||||
|
/// member entirely
|
||||||
|
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||||
/// whether a syntax error occurred
|
/// whether a syntax error occurred
|
||||||
bool errored = false;
|
bool errored = false;
|
||||||
/// callback function
|
/// callback function
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
|
#include <nlohmann/detail/input/number_parse.hpp>
|
||||||
#include <nlohmann/detail/input/position_t.hpp>
|
#include <nlohmann/detail/input/position_t.hpp>
|
||||||
|
#include <nlohmann/detail/input/string_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
|
|
||||||
@@ -125,6 +127,25 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter exposes a contiguous byte block that the
|
||||||
|
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
||||||
|
// Adapters without the flag - file, stream, wide-string, user-defined - fall
|
||||||
|
// back to the character-at-a-time string scanner.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_bulk_scan = decltype(InputAdapterType::supports_bulk_scan);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_bulk_scan(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_bulk_scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -146,13 +167,22 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||||
|
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||||
|
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||||
|
/// the per-character capture in get() cannot lose error diagnostics.
|
||||||
|
static constexpr bool bulk_scan =
|
||||||
|
lazy_token_string
|
||||||
|
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept
|
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||||
: ia(std::move(adapter))
|
: ia(std::move(adapter))
|
||||||
, ignore_comments(ignore_comments_)
|
, ignore_comments(ignore_comments_)
|
||||||
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
, decimal_point_char(static_cast<char_int_type>(get_decimal_point()))
|
||||||
|
, discard_number_values(discard_number_values_)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
// deleted because of pointer members
|
// deleted because of pointer members
|
||||||
@@ -265,6 +295,40 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
|
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
||||||
|
/// the first byte that needs individual handling (the closing quote, an
|
||||||
|
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
||||||
|
void scan_string_bulk(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
// a pending unget must be consumed through the normal path first
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
|
if (remaining == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
|
||||||
|
const std::size_t pos = string_bulk_run(data, remaining);
|
||||||
|
if (pos == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
|
ia.bulk_skip(pos);
|
||||||
|
// the run contains no newline (all bytes < 0x20 are treated as special),
|
||||||
|
// so only the flat character counters advance
|
||||||
|
position.chars_read_total += pos;
|
||||||
|
position.chars_read_current_line += pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: no bulk fast path
|
||||||
|
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
|
|
||||||
@@ -290,6 +354,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
|
// handle the next special byte through the switch below
|
||||||
|
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
|
||||||
// get the next character
|
// get the next character
|
||||||
switch (get())
|
switch (get())
|
||||||
{
|
{
|
||||||
@@ -1008,6 +1076,12 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
// changed if minus sign, decimal point, or exponent is read
|
// changed if minus sign, decimal point, or exponent is read
|
||||||
token_type number_type = token_type::value_unsigned;
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
|
||||||
|
// offset just past the last mantissa byte in token_buffer (i.e. the
|
||||||
|
// index of 'e'/'E', or the whole token when there is no exponent).
|
||||||
|
// convert_number() uses it to count significant digits; npos means
|
||||||
|
// "not seen an exponent yet" and is resolved at scan_number_done
|
||||||
|
std::size_t mantissa_end = std::string::npos;
|
||||||
|
|
||||||
// state (init): we just found out we need to scan a number
|
// state (init): we just found out we need to scan a number
|
||||||
switch (current)
|
switch (current)
|
||||||
{
|
{
|
||||||
@@ -1193,6 +1267,9 @@ scan_number_decimal2:
|
|||||||
scan_number_exponent:
|
scan_number_exponent:
|
||||||
// we just parsed an exponent
|
// we just parsed an exponent
|
||||||
number_type = token_type::value_float;
|
number_type = token_type::value_float;
|
||||||
|
// this label is reached only right after the 'e'/'E' was appended (from
|
||||||
|
// the zero, any1, and decimal2 states), so the mantissa ends before it
|
||||||
|
mantissa_end = token_buffer.size() - 1;
|
||||||
switch (get())
|
switch (get())
|
||||||
{
|
{
|
||||||
case '+':
|
case '+':
|
||||||
@@ -1279,45 +1356,199 @@ scan_number_done:
|
|||||||
// we are done scanning a number)
|
// we are done scanning a number)
|
||||||
unget();
|
unget();
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
// no exponent was scanned: the mantissa spans the whole token
|
||||||
errno = 0;
|
if (mantissa_end == std::string::npos)
|
||||||
|
{
|
||||||
|
mantissa_end = token_buffer.size();
|
||||||
|
}
|
||||||
|
|
||||||
// try to parse integers first and fall back to floats
|
return convert_number(number_type, mantissa_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert an already-validated integer token to its value
|
||||||
|
|
||||||
|
The digit sequence in [first, last) has been validated by the caller, so a
|
||||||
|
dedicated parser can avoid the locale/errno overhead of std::strtoull.
|
||||||
|
|
||||||
|
@return the token type on success; token_type::uninitialized if @a
|
||||||
|
number_type is not an integer type or the value does not fit, in
|
||||||
|
which case the caller falls back to the floating-point conversion
|
||||||
|
(matching the previous std::strtoull/std::strtoll behavior)
|
||||||
|
*/
|
||||||
|
token_type convert_integer(token_type number_type, const char* first, const char* last)
|
||||||
|
{
|
||||||
if (number_type == token_type::value_unsigned)
|
if (number_type == token_type::value_unsigned)
|
||||||
{
|
{
|
||||||
const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
|
if (parse_integer_unsigned(first, last, value_unsigned))
|
||||||
|
|
||||||
// we checked the number format before
|
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
|
||||||
|
|
||||||
if (errno != ERANGE)
|
|
||||||
{
|
|
||||||
value_unsigned = static_cast<number_unsigned_t>(x);
|
|
||||||
if (value_unsigned == x)
|
|
||||||
{
|
{
|
||||||
return token_type::value_unsigned;
|
return token_type::value_unsigned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (number_type == token_type::value_integer)
|
else if (number_type == token_type::value_integer)
|
||||||
{
|
{
|
||||||
const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
|
if (parse_integer_signed(first, last, value_integer))
|
||||||
|
|
||||||
// we checked the number format before
|
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
|
||||||
|
|
||||||
if (errno != ERANGE)
|
|
||||||
{
|
|
||||||
value_integer = static_cast<number_integer_t>(x);
|
|
||||||
if (value_integer == x)
|
|
||||||
{
|
{
|
||||||
return token_type::value_integer;
|
return token_type::value_integer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief check whether Clinger's fast path can still succeed for this token
|
||||||
|
|
||||||
|
parse_float_fast() needs a significand below 2^53. A mantissa with 17 or
|
||||||
|
more significant digits is at least 10^16 and therefore always exceeds it,
|
||||||
|
so calling the fast path would walk the token one extra time only to
|
||||||
|
decline before strtod has to run anyway.
|
||||||
|
|
||||||
|
Significant digits are the mantissa's digits from the first nonzero one on;
|
||||||
|
the sign, the decimal point, leading zeros, and the exponent do not count.
|
||||||
|
The answer is derived from indices - the digits are not scanned again - so
|
||||||
|
this stays off the hot path of the number scanners.
|
||||||
|
|
||||||
|
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||||
|
token_buffer
|
||||||
|
@return false if parse_float_fast() is guaranteed to decline
|
||||||
|
*/
|
||||||
|
bool mantissa_fits_clinger(std::size_t mantissa_end) const
|
||||||
|
{
|
||||||
|
// 10^16 already exceeds 2^53, so 17 digits can never fit
|
||||||
|
constexpr std::size_t limit = 17;
|
||||||
|
|
||||||
|
const std::size_t neg = (!token_buffer.empty() && token_buffer[0] == '-') ? 1u : 0u;
|
||||||
|
const std::size_t has_dot = (decimal_point_position != std::string::npos) ? 1u : 0u;
|
||||||
|
// the JSON grammar restricts the integer part to "0" or [1-9][0-9]*, so
|
||||||
|
// a leading zero can only be a lone "0", which is not significant
|
||||||
|
const std::size_t lead_zero = (token_buffer[neg] == '0') ? 1u : 0u;
|
||||||
|
JSON_ASSERT(mantissa_end >= neg + has_dot + lead_zero);
|
||||||
|
std::size_t digits = mantissa_end - neg - has_dot - lead_zero;
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_LIKELY(digits < limit))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only a number below 1 can carry further insignificant zeros, and only
|
||||||
|
// while the count stays at the limit does removing them change the
|
||||||
|
// answer - so this loop is skipped for all but a few tokens. Note
|
||||||
|
// token_buffer holds the locale's decimal point, so the fraction is
|
||||||
|
// located through decimal_point_position rather than by searching '.'.
|
||||||
|
if (lead_zero != 0)
|
||||||
|
{
|
||||||
|
JSON_ASSERT(has_dot != 0); // an integer "0" cannot reach the limit
|
||||||
|
for (std::size_t i = decimal_point_position + 1;
|
||||||
|
digits >= limit && i < mantissa_end && token_buffer[i] == '0'; ++i)
|
||||||
|
{
|
||||||
|
--digits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return digits < limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief convert the number text in token_buffer to its value and token type
|
||||||
|
|
||||||
|
The digit sequence in token_buffer has already been validated (by the
|
||||||
|
scan_number() state machine or by the contiguous fast path) and holds the
|
||||||
|
locale decimal point in place of '.'. Integers are parsed first and fall
|
||||||
|
back to floating point on overflow. This is shared so both scanners produce
|
||||||
|
identical results.
|
||||||
|
|
||||||
|
@param[in] mantissa_end offset just past the last mantissa byte in
|
||||||
|
token_buffer (the index of 'e'/'E', or
|
||||||
|
token_buffer.size() when there is no exponent);
|
||||||
|
used to skip Clinger's fast path when it cannot
|
||||||
|
possibly succeed - see mantissa_fits_clinger()
|
||||||
|
*/
|
||||||
|
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||||
|
{
|
||||||
|
// If the caller does not need the converted value (only whether the
|
||||||
|
// input is syntactically valid; see json_sax_acceptor/accept()), an
|
||||||
|
// unsigned/integer token can be reported without calling
|
||||||
|
// strtoull()/strtoll() at all, *provided* we can already tell from
|
||||||
|
// the digit count alone that the conversion cannot overflow 64 bits.
|
||||||
|
// Such tokens are always finite and are accepted unconditionally by
|
||||||
|
// the parser regardless of their actual value (parser::sax_parse_internal()
|
||||||
|
// never checks finiteness for value_unsigned/value_integer), so the
|
||||||
|
// classification below is all that is needed.
|
||||||
|
//
|
||||||
|
// A decimal number with up to 18 digits is always representable in
|
||||||
|
// both std::uint64_t and std::int64_t (18 nines is ~1e18, well below
|
||||||
|
// both UINT64_MAX ~1.8e19 and INT64_MAX ~9.2e18), so strtoull()/strtoll()
|
||||||
|
// could not have set errno to ERANGE for it. Numbers with more digits
|
||||||
|
// (rare in practice) fall through to the exact code below, unchanged,
|
||||||
|
// so their handling -- including reclassification to value_float when
|
||||||
|
// the value overflows 64 bits, and rejection when it is not even
|
||||||
|
// finite as a double -- is bit-for-bit identical to before this
|
||||||
|
// optimization.
|
||||||
|
//
|
||||||
|
// Note this reasons about std::uint64_t/std::int64_t, not about
|
||||||
|
// number_unsigned_t/number_integer_t (BasicJsonType's own, possibly
|
||||||
|
// narrower, template parameters -- e.g. std::uint32_t). That is fine
|
||||||
|
// *only* because discard_number_values is exclusively set by
|
||||||
|
// accept() (see json.hpp), and accept() always parses through the
|
||||||
|
// library's own json_sax_acceptor -- never a user-supplied SAX
|
||||||
|
// consumer -- whose number_unsigned()/number_integer()/number_float()
|
||||||
|
// callbacks unconditionally discard their argument and return true.
|
||||||
|
// So for every caller that can reach this branch, neither the token
|
||||||
|
// classification below nor the eventual (possibly narrowed, and on
|
||||||
|
// this fast path left stale/unset) value_unsigned/value_integer is
|
||||||
|
// ever consulted -- an unsigned/integer token is accepted outright,
|
||||||
|
// and even a >18-digit token that this fast path deliberately falls
|
||||||
|
// through for is, once reclassified to value_float, still finite
|
||||||
|
// (and thus accepted) for any digit count that fits in number_unsigned_t
|
||||||
|
// or number_integer_t regardless of that type's width. If this
|
||||||
|
// function is ever taught to run with discard_number_values true for
|
||||||
|
// a caller that *does* read the converted value, this reasoning (and
|
||||||
|
// the fast path below) would need to be revisited.
|
||||||
|
if (discard_number_values)
|
||||||
|
{
|
||||||
|
constexpr std::size_t safe_digit_count = 18;
|
||||||
|
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||||
|
{
|
||||||
|
return token_type::value_unsigned;
|
||||||
|
}
|
||||||
|
if (number_type == token_type::value_integer && token_buffer.size() - 1 <= safe_digit_count)
|
||||||
|
{
|
||||||
|
return token_type::value_integer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* const num_begin = token_buffer.data();
|
||||||
|
const char* const num_end = num_begin + token_buffer.size();
|
||||||
|
|
||||||
|
if (number_type != token_type::value_float)
|
||||||
|
{
|
||||||
|
const token_type integer_result = convert_integer(number_type, num_begin, num_end);
|
||||||
|
if (integer_result != token_type::uninitialized)
|
||||||
|
{
|
||||||
|
return integer_result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this code is reached if we parse a floating-point number or if an
|
// this code is reached if we parse a floating-point number or if an
|
||||||
// integer conversion above failed
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
|
// locale-aware strtof/strtod.
|
||||||
|
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||||
|
{
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
// Skipping a fast path that cannot succeed is lossless and saves a full
|
||||||
|
// extra pass over the token's bytes, which otherwise shows up on
|
||||||
|
// high-precision inputs such as canada.json
|
||||||
|
if (mantissa_fits_clinger(mantissa_end)
|
||||||
|
&& parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
|
||||||
|
{
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
strtof(value_float, token_buffer.data(), &endptr);
|
strtof(value_float, token_buffer.data(), &endptr);
|
||||||
|
|
||||||
// we checked the number format before
|
// we checked the number format before
|
||||||
@@ -1326,6 +1557,158 @@ scan_number_done:
|
|||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief contiguous fast path for scanning a number
|
||||||
|
|
||||||
|
Parses the whole number token straight from the input buffer, avoiding the
|
||||||
|
per-character get()/add() of scan_number(). On success it fills token_buffer
|
||||||
|
(with the locale decimal point substituted, as scan_number() does) and
|
||||||
|
returns the token type. On anything it does not fully recognize as a
|
||||||
|
well-formed number it makes no state change and returns
|
||||||
|
token_type::uninitialized, so the caller falls back to scan_number(), which
|
||||||
|
then produces the exact diagnostic. @a current is the first digit or the
|
||||||
|
leading minus (already read); the remaining bytes are taken from the adapter.
|
||||||
|
*/
|
||||||
|
token_type scan_number_bulk_contiguous()
|
||||||
|
{
|
||||||
|
// a pending unget offsets the buffer position from current; fall back
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
const std::size_t rem = ia.bulk_remaining();
|
||||||
|
if (rem == 0)
|
||||||
|
{
|
||||||
|
// the first digit is the last input byte; let scan_number() finish
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
// the byte before the next unread one is current (contiguous input)
|
||||||
|
const char* const data = reinterpret_cast<const char*>(ia.bulk_data()) - 1;
|
||||||
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
|
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||||
|
std::size_t i = 0;
|
||||||
|
std::size_t dot_index = std::string::npos;
|
||||||
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
if (data[0] == '-')
|
||||||
|
{
|
||||||
|
number_type = token_type::value_integer;
|
||||||
|
i = 1;
|
||||||
|
if (i >= avail)
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data[i] == '0')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
if (i < avail && data[i] == '.')
|
||||||
|
{
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
dot_index = i;
|
||||||
|
++i;
|
||||||
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the mantissa ends here, whether or not an exponent part follows
|
||||||
|
const std::size_t mantissa_end = i;
|
||||||
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
|
{
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
++i;
|
||||||
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const std::size_t len = i;
|
||||||
|
|
||||||
|
// reset() records where this token starts (for diagnostics), so it has
|
||||||
|
// to run before the input position advances below
|
||||||
|
reset();
|
||||||
|
|
||||||
|
// An integer token needs no token_buffer: the SAX callbacks for
|
||||||
|
// number_integer/number_unsigned take only the value, and the overflow
|
||||||
|
// diagnostic rebuilds the text from the input. Convert straight from the
|
||||||
|
// input buffer and leave token_buffer empty. (JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
// derives a number's start position from get_string().size(), so there
|
||||||
|
// the token still has to be materialized.)
|
||||||
|
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
if (number_type != token_type::value_float)
|
||||||
|
{
|
||||||
|
const token_type integer_result = convert_integer(number_type, data, data + len);
|
||||||
|
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
|
||||||
|
{
|
||||||
|
ia.bulk_skip(len - 1);
|
||||||
|
position.chars_read_total += (len - 1);
|
||||||
|
position.chars_read_current_line += (len - 1);
|
||||||
|
return integer_result;
|
||||||
|
}
|
||||||
|
// The value does not fit an integer, so this token converts as a
|
||||||
|
// float. Recording that here keeps convert_number() below from
|
||||||
|
// repeating the integer attempt that just failed.
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// materialize the token exactly as scan_number() would, substituting the
|
||||||
|
// locale decimal point so convert_number()'s strtof fallback stays valid.
|
||||||
|
// 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);
|
||||||
|
if (dot_index != std::string::npos)
|
||||||
|
{
|
||||||
|
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
|
||||||
|
decimal_point_position = dot_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
ia.bulk_skip(len - 1);
|
||||||
|
position.chars_read_total += (len - 1);
|
||||||
|
position.chars_read_current_line += (len - 1);
|
||||||
|
|
||||||
|
return convert_number(number_type, mantissa_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
const token_type t = scan_number_bulk_contiguous();
|
||||||
|
return (t != token_type::uninitialized) ? t : scan_number();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: always use the byte-path scanner
|
||||||
|
token_type scan_number_dispatch(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return scan_number();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] literal_text the literal text to expect
|
||||||
@param[in] length the length of the passed literal text
|
@param[in] length the length of the passed literal text
|
||||||
@@ -1357,6 +1740,11 @@ scan_number_done:
|
|||||||
token_buffer.clear();
|
token_buffer.clear();
|
||||||
decimal_point_position = std::string::npos;
|
decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
// the first character of the token has already been read, hence the -1
|
||||||
|
token_start_position = position.chars_read_total - 1;
|
||||||
|
#endif
|
||||||
|
|
||||||
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
note_token_start(std::integral_constant<bool, lazy_token_string> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1388,8 +1776,7 @@ scan_number_done:
|
|||||||
*/
|
*/
|
||||||
char_int_type get()
|
char_int_type get()
|
||||||
{
|
{
|
||||||
++position.chars_read_total;
|
advance_position();
|
||||||
++position.chars_read_current_line;
|
|
||||||
|
|
||||||
if (next_unget)
|
if (next_unget)
|
||||||
{
|
{
|
||||||
@@ -1401,6 +1788,23 @@ scan_number_done:
|
|||||||
current = ia.get_character();
|
current = ia.get_character();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return track_after_read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shared head of get() / get_ignoring_pending_unget(): bump the
|
||||||
|
/// per-character position counters (line-count-on-'\n' bookkeeping is
|
||||||
|
/// handled afterwards, in track_after_read(), once `current` is known)
|
||||||
|
void advance_position() noexcept
|
||||||
|
{
|
||||||
|
++position.chars_read_total;
|
||||||
|
++position.chars_read_current_line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// shared tail of get() / get_ignoring_pending_unget(): capture the
|
||||||
|
/// character for error messages (if needed) and update line/column
|
||||||
|
/// bookkeeping for the character now in `current`
|
||||||
|
char_int_type track_after_read()
|
||||||
|
{
|
||||||
// seekable adapters reconstruct the token lazily on error (see
|
// seekable adapters reconstruct the token lazily on error (see
|
||||||
// get_token_string), so the eager per-character copy is skipped
|
// get_token_string), so the eager per-character copy is skipped
|
||||||
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
capture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||||
@@ -1408,12 +1812,38 @@ scan_number_done:
|
|||||||
if (current == '\n')
|
if (current == '\n')
|
||||||
{
|
{
|
||||||
++position.lines_read;
|
++position.lines_read;
|
||||||
|
// remember the column the newline was read at: chars_read_current_line
|
||||||
|
// is about to be cleared, and a matching unget() cannot reconstruct it
|
||||||
|
chars_read_before_newline = position.chars_read_current_line;
|
||||||
position.chars_read_current_line = 0;
|
position.chars_read_current_line = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief like get(), but for call sites that can prove no unget() is pending
|
||||||
|
|
||||||
|
get() has to check the `next_unget` flag on every call, because a
|
||||||
|
previous token may have ended with unget() (e.g. scan_number() always
|
||||||
|
ungets the character that terminated the number, so the next call to
|
||||||
|
scan() can see it again). skip_whitespace() reads that first,
|
||||||
|
possibly-ungotten character via a plain get(), but every further
|
||||||
|
character it reads is guaranteed to be a fresh read: nothing between
|
||||||
|
those calls invokes unget(). This variant skips the (otherwise always
|
||||||
|
false) next_unget branch for those calls; it is not a general
|
||||||
|
replacement for get().
|
||||||
|
*/
|
||||||
|
char_int_type get_ignoring_pending_unget()
|
||||||
|
{
|
||||||
|
JSON_ASSERT(!next_unget);
|
||||||
|
|
||||||
|
advance_position();
|
||||||
|
current = ia.get_character();
|
||||||
|
|
||||||
|
return track_after_read();
|
||||||
|
}
|
||||||
|
|
||||||
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
/// seekable adapter: nothing to capture, the token is rebuilt on error
|
||||||
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
void capture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -1441,12 +1871,20 @@ scan_number_done:
|
|||||||
--position.chars_read_total;
|
--position.chars_read_total;
|
||||||
|
|
||||||
// in case we "unget" a newline, we have to also decrement the lines_read
|
// in case we "unget" a newline, we have to also decrement the lines_read
|
||||||
|
// and restore the column that get() cleared when it saw the newline;
|
||||||
|
// chars_read_current_line == 0 can only mean the last get() read one
|
||||||
if (position.chars_read_current_line == 0)
|
if (position.chars_read_current_line == 0)
|
||||||
{
|
{
|
||||||
if (position.lines_read > 0)
|
if (position.lines_read > 0)
|
||||||
{
|
{
|
||||||
--position.lines_read;
|
--position.lines_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// chars_read_before_newline counts the newline itself, which is the
|
||||||
|
// character being ungotten, hence the -1
|
||||||
|
position.chars_read_current_line = (chars_read_before_newline > 0)
|
||||||
|
? chars_read_before_newline - 1
|
||||||
|
: 0;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1519,6 +1957,15 @@ scan_number_done:
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
/// return the offset of the first character of the last read token; unlike
|
||||||
|
/// the token's parsed value, this accounts for escape sequences
|
||||||
|
constexpr std::size_t get_token_start_position() const noexcept
|
||||||
|
{
|
||||||
|
return token_start_position;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/// seekable adapter: rebuild the last read token from the input on demand
|
/// seekable adapter: rebuild the last read token from the input on demand
|
||||||
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
|
||||||
{
|
{
|
||||||
@@ -1598,13 +2045,37 @@ scan_number_done:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// whether `current` is one of the four JSON whitespace characters
|
||||||
|
bool current_is_whitespace() const noexcept
|
||||||
|
{
|
||||||
|
return current == ' ' || current == '\t' || current == '\n' || current == '\r';
|
||||||
|
}
|
||||||
|
|
||||||
void skip_whitespace()
|
void skip_whitespace()
|
||||||
{
|
{
|
||||||
|
// the first character may be a pending unget() left over from the
|
||||||
|
// previous token (see get_ignoring_pending_unget()); every
|
||||||
|
// subsequent character read by this loop is guaranteed fresh, since
|
||||||
|
// nothing below calls unget()
|
||||||
|
get();
|
||||||
|
|
||||||
|
if (!current_is_whitespace())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is written as an if-guarded do-while (rather than a plain
|
||||||
|
// while loop) because that shape is what lets both GCC and Clang
|
||||||
|
// keep the input adapter's read pointer in a register across
|
||||||
|
// iterations; the equivalent while-loop measurably defeated that
|
||||||
|
// optimization in testing, turning long whitespace runs (e.g. the
|
||||||
|
// indentation of pretty-printed JSON) from a register-only loop
|
||||||
|
// into one that reloads the pointer from memory every character
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
get();
|
get_ignoring_pending_unget();
|
||||||
}
|
}
|
||||||
while (current == ' ' || current == '\t' || current == '\n' || current == '\r');
|
while (current_is_whitespace());
|
||||||
}
|
}
|
||||||
|
|
||||||
token_type scan()
|
token_type scan()
|
||||||
@@ -1680,7 +2151,7 @@ scan_number_done:
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
return scan_number();
|
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
|
||||||
// end of input (the null byte is needed when parsing from
|
// end of input (the null byte is needed when parsing from
|
||||||
// string literals)
|
// string literals)
|
||||||
@@ -1711,6 +2182,10 @@ scan_number_done:
|
|||||||
/// the start position of the current token
|
/// the start position of the current token
|
||||||
position_t position {};
|
position_t position {};
|
||||||
|
|
||||||
|
/// the value chars_read_current_line had when the last newline was read, so
|
||||||
|
/// that unget() can restore the column instead of leaving it at 0
|
||||||
|
std::size_t chars_read_before_newline = 0;
|
||||||
|
|
||||||
/// raw input token string for error messages; only populated for streaming
|
/// raw input token string for error messages; only populated for streaming
|
||||||
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
|
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
|
||||||
std::vector<char_type> token_string {};
|
std::vector<char_type> token_string {};
|
||||||
@@ -1719,6 +2194,12 @@ scan_number_done:
|
|||||||
/// the last read token on error for seekable adapters (see collect_token_chars)
|
/// the last read token on error for seekable adapters (see collect_token_chars)
|
||||||
std::size_t token_string_start = 0;
|
std::size_t token_string_start = 0;
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
/// start offset of the current token within the input, used to report
|
||||||
|
/// diagnostic positions (see reset())
|
||||||
|
std::size_t token_start_position = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
/// buffer for variable-length tokens (numbers, strings)
|
/// buffer for variable-length tokens (numbers, strings)
|
||||||
string_t token_buffer {};
|
string_t token_buffer {};
|
||||||
|
|
||||||
@@ -1734,6 +2215,13 @@ scan_number_done:
|
|||||||
const char_int_type decimal_point_char = '.';
|
const char_int_type decimal_point_char = '.';
|
||||||
/// the position of the decimal point in the input
|
/// the position of the decimal point in the input
|
||||||
std::size_t decimal_point_position = std::string::npos;
|
std::size_t decimal_point_position = std::string::npos;
|
||||||
|
|
||||||
|
/// whether the caller (e.g. accept()/json_sax_acceptor) only needs the
|
||||||
|
/// token classification and never looks at the converted numeric value;
|
||||||
|
/// when set, scan_number() may skip strtoull()/strtoll() for
|
||||||
|
/// value_unsigned/value_integer tokens whose digit count guarantees they
|
||||||
|
/// fit into 64 bits (see scan_number())
|
||||||
|
const bool discard_number_values = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <cfloat> // FLT_EVAL_METHOD
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <limits> // numeric_limits
|
||||||
|
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||||
|
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||||
|
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||||
|
// include with __has_include so such toolchains fall back to the scalar path.
|
||||||
|
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||||
|
#if __has_include(<charconv>)
|
||||||
|
#include <charconv> // from_chars (only used when __cpp_lib_to_chars is defined)
|
||||||
|
#include <system_error> // errc
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||||
|
// already-validated number token into a value, without the locale/errno
|
||||||
|
// overhead of std::strtoull/std::strtod. They are free functions so the lexer
|
||||||
|
// stays focused on scanning; see lexer::convert_number().
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief fast integer parser for an already-validated unsigned integer
|
||||||
|
|
||||||
|
The number scanner has already checked that [first, last) is a valid JSON
|
||||||
|
integer, so this only needs to accumulate the digits and detect overflow. This
|
||||||
|
avoids the locale/errno machinery of std::strtoull, which dominates
|
||||||
|
integer-heavy inputs.
|
||||||
|
|
||||||
|
@param[in] first pointer to the first character (a digit)
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[out] value the parsed value on success
|
||||||
|
@return true if the value fit into @a NumberUnsignedType; false on overflow, in
|
||||||
|
which case the caller falls back to floating-point parsing (matching the
|
||||||
|
previous std::strtoull behavior)
|
||||||
|
*/
|
||||||
|
template<typename NumberUnsignedType>
|
||||||
|
bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedType& value) noexcept
|
||||||
|
{
|
||||||
|
// accumulate in the widest unsigned type used by the previous strtoull
|
||||||
|
// path so the overflow behavior is unchanged for custom number types
|
||||||
|
std::uint64_t x = 0;
|
||||||
|
constexpr std::uint64_t cutoff = (std::numeric_limits<std::uint64_t>::max)() / 10u;
|
||||||
|
constexpr std::uint64_t cutlim = (std::numeric_limits<std::uint64_t>::max)() % 10u;
|
||||||
|
for (const char* p = first; p != last; ++p)
|
||||||
|
{
|
||||||
|
const auto digit = static_cast<std::uint64_t>(static_cast<unsigned char>(*p) - static_cast<unsigned char>('0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(x > cutoff || (x == cutoff && digit > cutlim)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
x = (x * 10u) + digit;
|
||||||
|
}
|
||||||
|
value = static_cast<NumberUnsignedType>(x);
|
||||||
|
// reject values that do not round-trip into a narrower NumberUnsignedType
|
||||||
|
return static_cast<std::uint64_t>(value) == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief fast integer parser for an already-validated negative integer
|
||||||
|
|
||||||
|
@param[in] first pointer to the leading '-'
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[out] value the parsed (negative) value on success
|
||||||
|
@return true on success; false on overflow (caller falls back to float)
|
||||||
|
*/
|
||||||
|
template<typename NumberIntegerType>
|
||||||
|
bool parse_integer_signed(const char* first, const char* last, NumberIntegerType& value) noexcept
|
||||||
|
{
|
||||||
|
// the state machine only reaches the signed path via a leading '-'
|
||||||
|
JSON_ASSERT(first != last && *first == '-');
|
||||||
|
std::uint64_t magnitude = 0;
|
||||||
|
// |INT64_MIN| == INT64_MAX + 1; this is the largest admissible magnitude
|
||||||
|
constexpr std::uint64_t limit = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1u;
|
||||||
|
for (const char* p = first + 1; p != last; ++p)
|
||||||
|
{
|
||||||
|
const auto digit = static_cast<std::uint64_t>(static_cast<unsigned char>(*p) - static_cast<unsigned char>('0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(magnitude > (limit - digit) / 10u))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
magnitude = (magnitude * 10u) + digit;
|
||||||
|
}
|
||||||
|
const std::int64_t x = (magnitude == limit)
|
||||||
|
? (std::numeric_limits<std::int64_t>::min)()
|
||||||
|
: -static_cast<std::int64_t>(magnitude);
|
||||||
|
value = static_cast<NumberIntegerType>(x);
|
||||||
|
// reject values that do not round-trip into a narrower NumberIntegerType
|
||||||
|
return static_cast<std::int64_t>(value) == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief exact fast path for parsing a `double` (Clinger's algorithm)
|
||||||
|
|
||||||
|
For the common case - at most 19 significant digits, a decimal exponent in
|
||||||
|
[-22, 22], and a significand below 2^53 - the value equals significand *
|
||||||
|
10^exp computed in IEEE-754 double arithmetic, which is exact under
|
||||||
|
round-to-nearest because both operands are exactly representable. This is the
|
||||||
|
same fast path used by fast_float/simdjson; the general cases are left to
|
||||||
|
std::strtod. The parser only activates for number_float_t == double; float and
|
||||||
|
long double keep the std::strtof/std::strtold paths (see the templated overload
|
||||||
|
below).
|
||||||
|
|
||||||
|
@param[in] first pointer to the first character of the number
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[in] decimal_point the (locale-dependent) decimal point character
|
||||||
|
@param[out] out the parsed value on success
|
||||||
|
@return true if the value was parsed exactly; false to fall back to strtod
|
||||||
|
*/
|
||||||
|
template<typename DecimalPointType>
|
||||||
|
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
|
||||||
|
{
|
||||||
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
|
// Clinger's fast path is only exact when double operations are evaluated in
|
||||||
|
// true double precision. On platforms that keep intermediates in extended
|
||||||
|
// precision (e.g. the x87 FPU on 32-bit x86, where FLT_EVAL_METHOD == 2) the
|
||||||
|
// single significand * 10^scale step is double-rounded and can be 1 ULP off,
|
||||||
|
// so decline and let the caller fall back to the correctly-rounded
|
||||||
|
// std::from_chars / std::strtod path.
|
||||||
|
static_cast<void>(first);
|
||||||
|
static_cast<void>(last);
|
||||||
|
static_cast<void>(decimal_point);
|
||||||
|
static_cast<void>(out);
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
static const std::array<double, 23> powers_of_ten =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
|
||||||
|
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* p = first;
|
||||||
|
bool negative = false;
|
||||||
|
if (p != last && (*p == '-' || *p == '+'))
|
||||||
|
{
|
||||||
|
negative = (*p == '-');
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t significand = 0;
|
||||||
|
int num_digits = 0;
|
||||||
|
int fractional_digits = 0;
|
||||||
|
bool seen_dot = false;
|
||||||
|
bool any_digit = false;
|
||||||
|
for (; p != last; ++p)
|
||||||
|
{
|
||||||
|
const char c = *p;
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
any_digit = true;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(num_digits >= 19))
|
||||||
|
{
|
||||||
|
return false; // significand may not fit into uint64_t
|
||||||
|
}
|
||||||
|
significand = (significand * 10u) + static_cast<std::uint64_t>(c - '0');
|
||||||
|
++num_digits;
|
||||||
|
fractional_digits += static_cast<int>(seen_dot);
|
||||||
|
}
|
||||||
|
else if (static_cast<DecimalPointType>(c) == decimal_point)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen_dot = true;
|
||||||
|
}
|
||||||
|
else if (c == 'e' || c == 'E')
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!any_digit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exponent = 0;
|
||||||
|
if (p != last) // an exponent part remains
|
||||||
|
{
|
||||||
|
bool exp_negative = false;
|
||||||
|
if (p != last && (*p == '-' || *p == '+'))
|
||||||
|
{
|
||||||
|
exp_negative = (*p == '-');
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
bool any_exp_digit = false;
|
||||||
|
for (; p != last; ++p)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(*p < '0' || *p > '9'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
exponent = (exponent * 10) + (*p - '0');
|
||||||
|
any_exp_digit = true;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(exponent > 9999))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!any_exp_digit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (exp_negative)
|
||||||
|
{
|
||||||
|
exponent = -exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int scale = exponent - fractional_digits;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(significand >= (static_cast<std::uint64_t>(1) << 53)))
|
||||||
|
{
|
||||||
|
return false; // significand not exactly representable as double
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = static_cast<double>(significand);
|
||||||
|
if (scale >= 0)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(scale > 22))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result *= powers_of_ten[static_cast<std::size_t>(scale)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(-scale > 22))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result /= powers_of_ten[static_cast<std::size_t>(-scale)];
|
||||||
|
}
|
||||||
|
out = negative ? -result : result;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fast float path is only exact for `double`; decline for float/long double
|
||||||
|
template<typename DecimalPointType, typename FloatType>
|
||||||
|
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse a float with std::from_chars (Eisel-Lemire) when available
|
||||||
|
|
||||||
|
std::from_chars is locale-independent, correctly rounded, and - via the
|
||||||
|
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
||||||
|
over the whole value range (not just the Clinger subset). It is used only when
|
||||||
|
__cpp_lib_to_chars indicates full floating-point support and only when it
|
||||||
|
consumes the entire token ([first, last)); a partial parse means the buffer
|
||||||
|
uses a non-'.' locale decimal point, in which case the caller falls back to the
|
||||||
|
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
|
||||||
|
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
||||||
|
expects (side-stepping the P4168 divergence between implementations).
|
||||||
|
|
||||||
|
@return true if the value was parsed exactly and fully; false to fall back
|
||||||
|
*/
|
||||||
|
template<typename FloatType>
|
||||||
|
bool parse_float_from_chars(const char* first, const char* last, FloatType& out) noexcept
|
||||||
|
{
|
||||||
|
// JSON_HAS_CPP_17 must gate the use as well as the <charconv> include above:
|
||||||
|
// some standard libraries (e.g. libstdc++ 15) define __cpp_lib_to_chars even
|
||||||
|
// in C++14 mode, where <charconv> is not included.
|
||||||
|
#if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars)
|
||||||
|
const auto result = std::from_chars(first, last, out);
|
||||||
|
return result.ec == std::errc() && result.ptr == last;
|
||||||
|
#else
|
||||||
|
static_cast<void>(first);
|
||||||
|
static_cast<void>(last);
|
||||||
|
static_cast<void>(out);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -72,9 +72,10 @@ class parser
|
|||||||
parser_callback_t<BasicJsonType> cb = nullptr,
|
parser_callback_t<BasicJsonType> cb = nullptr,
|
||||||
const bool allow_exceptions_ = true,
|
const bool allow_exceptions_ = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas_ = false)
|
const bool ignore_trailing_commas_ = false,
|
||||||
|
const bool discard_number_values_ = false)
|
||||||
: callback(std::move(cb))
|
: callback(std::move(cb))
|
||||||
, m_lexer(std::move(adapter), ignore_comments)
|
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
||||||
, allow_exceptions(allow_exceptions_)
|
, allow_exceptions(allow_exceptions_)
|
||||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,287 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint64_t
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external
|
||||||
|
// dependency: nlohmann/json itself stays header-only and the C++11 scalar
|
||||||
|
// validator below is always available; defining JSON_USE_SIMDUTF additionally
|
||||||
|
// requires the simdutf headers on the include path and linking the simdutf
|
||||||
|
// library. See string_bulk_run().
|
||||||
|
//
|
||||||
|
// simdutf.h itself requires C++17 - it rejects older standards with an #error -
|
||||||
|
// so the backend is only compiled in from C++17 on. Below that the macro has no
|
||||||
|
// effect and the scalar validator is used; it accepts and rejects exactly the
|
||||||
|
// same input, so only throughput differs. macro_scope.hpp is included above to
|
||||||
|
// have JSON_HAS_CPP_17 available for this test.
|
||||||
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
|
#include <simdutf.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||||
|
// itself focused on the state machine; see lexer::scan_string_bulk().
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
// classify a single byte as needing individual string handling: the closing
|
||||||
|
// quote, an escape, a control character, or a non-ASCII (UTF-8)
|
||||||
|
// lead/continuation byte. Ordinary bytes (0x20..0x7F except '"' and '\\') are
|
||||||
|
// copied verbatim, which the bulk scanner does 8 bytes at a time.
|
||||||
|
inline bool is_string_special(unsigned char c) noexcept
|
||||||
|
{
|
||||||
|
return c == '\"' || c == '\\' || c < 0x20u || c >= 0x80u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SWAR helper: return a word whose high bit is set in every byte of @a v that
|
||||||
|
// is_string_special(); zero if the 8 bytes are all ordinary.
|
||||||
|
inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||||
|
const std::uint64_t has_quote = (q - ones) & ~q & high;
|
||||||
|
const std::uint64_t has_backslash = (b - ones) & ~b & high;
|
||||||
|
const std::uint64_t has_control = (v - 0x2020202020202020ull) & ~v & high; // < 0x20
|
||||||
|
const std::uint64_t has_non_ascii = v & high; // >= 0x80
|
||||||
|
return has_quote | has_backslash | has_control | has_non_ascii;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
std::uint64_t word = 0;
|
||||||
|
std::memcpy(&word, data + i, sizeof(word));
|
||||||
|
if (swar_string_special(word) != 0)
|
||||||
|
{
|
||||||
|
// a special byte is in this word; locate it (endian-agnostic)
|
||||||
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
if (is_string_special(data[i + j]))
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (is_string_special(data[i]))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// classify a byte as one the serializer must NOT copy verbatim when
|
||||||
|
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||||
|
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||||
|
// 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
|
||||||
|
// \u007f under ensure_ascii).
|
||||||
|
inline bool is_ascii_copyable(unsigned char c) noexcept
|
||||||
|
{
|
||||||
|
return c >= 0x20u && c < 0x7Fu && c != '"' && c != '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the index of the first byte in [data, data+n) that is NOT
|
||||||
|
// is_ascii_copyable(), or n if every byte can be copied verbatim; scans 8 bytes
|
||||||
|
// at a time. Used by the serializer's ensure_ascii fast path.
|
||||||
|
inline std::size_t find_ascii_copyable_run(const unsigned char* data, 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)
|
||||||
|
{
|
||||||
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||||
|
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||||
|
const std::uint64_t stop = ((q - ones) & ~q & high) // == '"'
|
||||||
|
| ((b - ones) & ~b & high) // == '\\'
|
||||||
|
| ((d - ones) & ~d & high) // == 0x7F
|
||||||
|
| ((v - 0x2020202020202020ull) & ~v & high) // < 0x20
|
||||||
|
| (v & high); // >= 0x80
|
||||||
|
if (stop != 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (!is_ascii_copyable(data[i]))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate one UTF-8 sequence at the front of [data, data+avail). Returns its
|
||||||
|
// length (2..4) only when the bytes form a *well-formed* sequence using exactly
|
||||||
|
// the same ranges as scan_string()'s per-byte switch, so the bulk path accepts
|
||||||
|
// precisely what the byte path accepts. Returns 0 for anything that is invalid,
|
||||||
|
// incomplete, or that the byte path must diagnose (the caller then defers to
|
||||||
|
// that path, keeping error messages unchanged). Lead bytes < 0x80 are handled
|
||||||
|
// by the caller and never passed here.
|
||||||
|
inline std::size_t validate_one_utf8(const unsigned char* data, std::size_t avail) noexcept
|
||||||
|
{
|
||||||
|
const unsigned char c0 = data[0];
|
||||||
|
if (c0 >= 0xC2 && c0 <= 0xDF) // U+0080..U+07FF
|
||||||
|
{
|
||||||
|
if (avail >= 2 && data[1] >= 0x80 && data[1] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xE0) // U+0800..U+0FFF
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0xA0 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((c0 >= 0xE1 && c0 <= 0xEC) || c0 == 0xEE || c0 == 0xEF) // U+1000..U+CFFF, U+E000..U+FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xED) // U+D000..U+D7FF (excludes surrogates)
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0x9F && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xF0) // U+10000..U+3FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x90 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 >= 0xF1 && c0 <= 0xF3) // U+40000..U+FFFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xF4) // U+100000..U+10FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0x8F && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // invalid, incomplete, or must be diagnosed by the byte path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar (C++11) 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
|
||||||
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||||
|
// sequence). ASCII is skipped 8 bytes at a time.
|
||||||
|
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::size_t pos = 0;
|
||||||
|
while (pos < n)
|
||||||
|
{
|
||||||
|
pos += find_string_special(data + pos, n - pos);
|
||||||
|
if (pos >= n || data[pos] < 0x80u)
|
||||||
|
{
|
||||||
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
|
}
|
||||||
|
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||||
|
if (seq == 0)
|
||||||
|
{
|
||||||
|
break; // ill-formed or truncated: let the byte path diagnose it
|
||||||
|
}
|
||||||
|
pos += seq;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
|
// Index of the first quote/escape/control byte in [data, data+n) (non-ASCII
|
||||||
|
// bytes are *not* stops here - the whole run is handed to simdutf), or n.
|
||||||
|
inline std::size_t find_string_delimiter(const unsigned char* data, 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)
|
||||||
|
{
|
||||||
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
||||||
|
const std::uint64_t hit = ((q - ones) & ~q & high)
|
||||||
|
| ((b - ones) & ~b & high)
|
||||||
|
| ((v - 0x2020202020202020ull) & ~v & high);
|
||||||
|
if (hit != 0)
|
||||||
|
{
|
||||||
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
const unsigned char c = data[i + j];
|
||||||
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char c = data[i];
|
||||||
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Backend-dispatched bulk run length. With JSON_USE_SIMDUTF the run up to 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
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||||
|
const std::size_t run = find_string_delimiter(data, n);
|
||||||
|
if (run != 0 && simdutf::validate_utf8(reinterpret_cast<const char*>(data), run))
|
||||||
|
{
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return scalar_string_bulk_run(data, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
#include <nlohmann/detail/string_utils.hpp>
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.hpp>
|
#include <nlohmann/detail/value_t.hpp>
|
||||||
@@ -206,10 +207,10 @@ NLOHMANN_JSON_NAMESPACE_END
|
|||||||
namespace std
|
namespace std
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Fix: https://github.com/nlohmann/json/issues/1401
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
// Fix: https://github.com/nlohmann/json/issues/1401
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma clang diagnostic push
|
JSON_HEDLEY_PRAGMA(clang diagnostic ignored "-Wmismatched-tags")
|
||||||
#pragma clang diagnostic ignored "-Wmismatched-tags"
|
|
||||||
#endif
|
#endif
|
||||||
template<typename IteratorType>
|
template<typename IteratorType>
|
||||||
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
|
class tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)
|
||||||
@@ -224,7 +225,7 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
|
|||||||
::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
|
::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));
|
||||||
};
|
};
|
||||||
#if defined(__clang__)
|
#if defined(__clang__)
|
||||||
#pragma clang diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
|||||||
@@ -748,6 +748,20 @@ class json_pointer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the reference token consists only of digits at this point (cf. checks
|
||||||
|
// above); however, its numeric value might not be representable, in which
|
||||||
|
// case array_index() would throw out_of_range.404/410 -- contains() must
|
||||||
|
// not throw (see #5395), so such a reference token is treated as "not found"
|
||||||
|
errno = 0; // strtoull() does not reset errno on success
|
||||||
|
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||||
|
const unsigned long long magnitude = std::strtoull(reference_token.c_str(), &p_end, 10); // NOLINT(runtime/int)
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(errno == ERANGE // the value exceeds ULLONG_MAX
|
||||||
|
|| magnitude >= static_cast<unsigned long long>((std::numeric_limits<typename BasicJsonType::size_type>::max)()))) // NOLINT(runtime/int)
|
||||||
|
{
|
||||||
|
// the array index cannot be represented as size_type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||||
if (idx >= ptr->size())
|
if (idx >= ptr->size())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <tuple> // tuple
|
#include <tuple> // tuple
|
||||||
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
||||||
#include <utility> // declval
|
#include <utility> // declval
|
||||||
|
#include <vector> // vector
|
||||||
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
||||||
#include <cstddef> // byte
|
#include <cstddef> // byte
|
||||||
#endif
|
#endif
|
||||||
@@ -230,7 +231,9 @@ struct char_traits<signed char> : std::char_traits<char>
|
|||||||
// Redefine to_int_type function
|
// Redefine to_int_type function
|
||||||
static int_type to_int_type(char_type c) noexcept
|
static int_type to_int_type(char_type c) noexcept
|
||||||
{
|
{
|
||||||
return static_cast<int_type>(c);
|
// cast via unsigned char: sign-extending a negative char_type would make
|
||||||
|
// byte 0xFF indistinguishable from eof()
|
||||||
|
return static_cast<int_type>(static_cast<unsigned char>(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
static char_type to_char_type(int_type i) noexcept
|
static char_type to_char_type(int_type i) noexcept
|
||||||
@@ -632,6 +635,14 @@ template<typename BasicJsonType, typename CompatibleType>
|
|||||||
struct is_compatible_type
|
struct is_compatible_type
|
||||||
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
|
||||||
|
|
||||||
|
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||||
|
struct is_compatible_binary_type
|
||||||
|
{
|
||||||
|
static constexpr bool value =
|
||||||
|
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
|
||||||
|
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
|
||||||
|
};
|
||||||
|
|
||||||
template<typename BasicJsonType, typename CompatibleReferenceType>
|
template<typename BasicJsonType, typename CompatibleReferenceType>
|
||||||
struct is_compatible_reference_type_impl
|
struct is_compatible_reference_type_impl
|
||||||
{
|
{
|
||||||
@@ -690,21 +701,35 @@ struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>> : std::true_type {};
|
|||||||
template <typename A>
|
template <typename A>
|
||||||
struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {};
|
struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {};
|
||||||
|
|
||||||
// checks if A and B are comparable using Compare functor
|
// checks if A and B are comparable using Compare functor, assuming that
|
||||||
|
// neither A nor B is a json_pointer type (that case is handled by
|
||||||
|
// is_comparable below, which never instantiates this helper otherwise)
|
||||||
template<typename Compare, typename A, typename B, typename = void>
|
template<typename Compare, typename A, typename B, typename = void>
|
||||||
struct is_comparable : std::false_type {};
|
struct is_comparable_no_json_pointer : std::false_type {};
|
||||||
|
|
||||||
// We exclude json_pointer here, because the checks using Compare(A, B) will
|
|
||||||
// use json_pointer::operator string_t() which triggers a deprecation warning
|
|
||||||
// for GCC. See https://github.com/nlohmann/json/issues/4621. The call to
|
|
||||||
// is_json_pointer_of can be removed once the deprecated function has been
|
|
||||||
// removed.
|
|
||||||
template<typename Compare, typename A, typename B>
|
template<typename Compare, typename A, typename B>
|
||||||
struct is_comparable < Compare, A, B, enable_if_t < !is_json_pointer_of<A, B>::value
|
struct is_comparable_no_json_pointer < Compare, A, B, enable_if_t <
|
||||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
|
||||||
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
|
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
|
||||||
>> : std::true_type {};
|
>> : std::true_type {};
|
||||||
|
|
||||||
|
// checks if A and B are comparable using Compare functor
|
||||||
|
// We dispatch on is_json_pointer_of as a plain bool (rather than folding it
|
||||||
|
// into a single enable_if_t condition together with the checks below) so
|
||||||
|
// that the Compare(A, B) checks are only ever written - and thus only ever
|
||||||
|
// instantiated - when A/B are not a json_pointer/string pair. Those checks
|
||||||
|
// use json_pointer::operator string_t() (GCC, see #4621) resp. the
|
||||||
|
// deprecated json_pointer/string operator== (Clang, see #5288), and merely
|
||||||
|
// naming them as later operands of a plain && chain is not sufficient to
|
||||||
|
// avoid their instantiation on all compilers, even when the first operand
|
||||||
|
// is false. The dispatch on is_json_pointer_of can be removed once the
|
||||||
|
// deprecated json_pointer comparison operators have been removed.
|
||||||
|
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
|
||||||
|
struct is_comparable : std::false_type {};
|
||||||
|
|
||||||
|
template<typename Compare, typename A, typename B>
|
||||||
|
struct is_comparable<Compare, A, B, false> : is_comparable_no_json_pointer<Compare, A, B> {};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
using detect_is_transparent = typename T::is_transparent;
|
using detect_is_transparent = typename T::is_transparent;
|
||||||
|
|
||||||
|
|||||||
@@ -813,7 +813,10 @@ class binary_writer
|
|||||||
bool prefix_required = true;
|
bool prefix_required = true;
|
||||||
if (use_type && !j.m_data.m_value.array->empty())
|
if (use_type && !j.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
JSON_ASSERT(use_count);
|
if (!use_count)
|
||||||
|
{
|
||||||
|
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j));
|
||||||
|
}
|
||||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||||
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
const bool same_prefix = std::all_of(j.begin() + 1, j.end(),
|
||||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||||
@@ -823,7 +826,17 @@ class binary_writer
|
|||||||
|
|
||||||
std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type
|
std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type
|
||||||
|
|
||||||
if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))
|
// an optimized array of a valueless type carries no payload, so a
|
||||||
|
// reader has nothing but the declared count to bound the allocation
|
||||||
|
// by and refuses an excessive one. Write the unoptimized form for
|
||||||
|
// those, at one byte per element, so the result can be read back.
|
||||||
|
// Objects are not affected: every element is preceded by its key.
|
||||||
|
const bool valueless_type = (first_prefix == 'Z' || first_prefix == 'T' || first_prefix == 'F');
|
||||||
|
const bool excessive_valueless = valueless_type
|
||||||
|
&& j.m_data.m_value.array->size() > detail::max_valueless_container_size;
|
||||||
|
|
||||||
|
if (same_prefix && !excessive_valueless
|
||||||
|
&& !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))
|
||||||
{
|
{
|
||||||
prefix_required = false;
|
prefix_required = false;
|
||||||
oa->write_character(to_char_type('$'));
|
oa->write_character(to_char_type('$'));
|
||||||
@@ -859,7 +872,10 @@ class binary_writer
|
|||||||
|
|
||||||
if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty()))
|
if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty()))
|
||||||
{
|
{
|
||||||
JSON_ASSERT(use_count);
|
if (!use_count)
|
||||||
|
{
|
||||||
|
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j));
|
||||||
|
}
|
||||||
oa->write_character(to_char_type('$'));
|
oa->write_character(to_char_type('$'));
|
||||||
oa->write_character(bjdata_draft3 ? 'B' : 'U');
|
oa->write_character(bjdata_draft3 ? 'B' : 'U');
|
||||||
}
|
}
|
||||||
@@ -911,7 +927,10 @@ class binary_writer
|
|||||||
bool prefix_required = true;
|
bool prefix_required = true;
|
||||||
if (use_type && !j.m_data.m_value.object->empty())
|
if (use_type && !j.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
JSON_ASSERT(use_count);
|
if (!use_count)
|
||||||
|
{
|
||||||
|
JSON_THROW(other_error::create(502, "use_type requires use_size = true", &j));
|
||||||
|
}
|
||||||
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);
|
||||||
const bool same_prefix = std::all_of(j.begin(), j.end(),
|
const bool same_prefix = std::all_of(j.begin(), j.end(),
|
||||||
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
[this, first_prefix, use_bjdata](const BasicJsonType & v)
|
||||||
@@ -979,13 +998,28 @@ class binary_writer
|
|||||||
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Checks that @a size fits into the 32-bit length field used by BSON
|
||||||
|
@return The size as a signed 32-bit integer
|
||||||
|
@throw out_of_range.412 if @a size exceeds the range of std::int32_t
|
||||||
|
*/
|
||||||
|
static std::int32_t to_bson_length(const std::size_t size)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
||||||
|
{
|
||||||
|
JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<std::int32_t>(size);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Writes the given @a element_type and @a name to the output adapter
|
@brief Writes the given @a element_type and @a name to the output adapter
|
||||||
*/
|
*/
|
||||||
void write_bson_entry_header(const string_t& name,
|
void write_bson_entry_header(const string_t& name,
|
||||||
const std::uint8_t element_type)
|
const std::uint8_t element_type)
|
||||||
{
|
{
|
||||||
oa->write_character(to_char_type(element_type)); // boolean
|
oa->write_character(to_char_type(element_type));
|
||||||
oa->write_characters(
|
oa->write_characters(
|
||||||
reinterpret_cast<const CharType*>(name.c_str()),
|
reinterpret_cast<const CharType*>(name.c_str()),
|
||||||
name.size() + 1u);
|
name.size() + 1u);
|
||||||
@@ -1027,7 +1061,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x02);
|
write_bson_entry_header(name, 0x02);
|
||||||
|
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);
|
write_number<std::int32_t>(to_bson_length(value.size() + 1ul), true);
|
||||||
oa->write_characters(
|
oa->write_characters(
|
||||||
reinterpret_cast<const CharType*>(value.c_str()),
|
reinterpret_cast<const CharType*>(value.c_str()),
|
||||||
value.size() + 1);
|
value.size() + 1);
|
||||||
@@ -1070,7 +1104,7 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return The size of the BSON-encoded unsigned integer in @a j
|
@return The size of the BSON-encoded unsigned integer @a value
|
||||||
*/
|
*/
|
||||||
static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
|
static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
|
||||||
{
|
{
|
||||||
@@ -1083,22 +1117,22 @@ class binary_writer
|
|||||||
@brief Writes a BSON element with key @a name and unsigned @a value
|
@brief Writes a BSON element with key @a name and unsigned @a value
|
||||||
*/
|
*/
|
||||||
void write_bson_unsigned(const string_t& name,
|
void write_bson_unsigned(const string_t& name,
|
||||||
const BasicJsonType& j)
|
const std::uint64_t value)
|
||||||
{
|
{
|
||||||
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x10 /* int32 */);
|
write_bson_entry_header(name, 0x10 /* int32 */);
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(value), true);
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x12 /* int64 */);
|
write_bson_entry_header(name, 0x12 /* int64 */);
|
||||||
write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true);
|
write_number<std::int64_t>(static_cast<std::int64_t>(value), true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x11 /* uint64 */);
|
write_bson_entry_header(name, 0x11 /* uint64 */);
|
||||||
write_number<std::uint64_t>(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned), true);
|
write_number<std::uint64_t>(value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,7 +1176,7 @@ class binary_writer
|
|||||||
const typename BasicJsonType::array_t& value)
|
const typename BasicJsonType::array_t& value)
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x04); // array
|
write_bson_entry_header(name, 0x04); // array
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);
|
write_number<std::int32_t>(to_bson_length(calc_bson_array_size(value)), true);
|
||||||
|
|
||||||
std::size_t array_index = 0ul;
|
std::size_t array_index = 0ul;
|
||||||
|
|
||||||
@@ -1162,7 +1196,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x05);
|
write_bson_entry_header(name, 0x05);
|
||||||
|
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);
|
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
||||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||||
|
|
||||||
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||||
@@ -1244,7 +1278,7 @@ class binary_writer
|
|||||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
return write_bson_unsigned(name, j);
|
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
return write_bson_string(name, *j.m_data.m_value.string);
|
return write_bson_string(name, *j.m_data.m_value.string);
|
||||||
@@ -1284,7 +1318,7 @@ class binary_writer
|
|||||||
*/
|
*/
|
||||||
void write_bson_object(const typename BasicJsonType::object_t& value)
|
void write_bson_object(const typename BasicJsonType::object_t& value)
|
||||||
{
|
{
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);
|
write_number<std::int32_t>(to_bson_length(calc_bson_object_size(value)), true);
|
||||||
|
|
||||||
for (const auto& el : value)
|
for (const auto& el : value)
|
||||||
{
|
{
|
||||||
@@ -1623,6 +1657,20 @@ class binary_writer
|
|||||||
return 'D'; // float 64
|
return 'D'; // float 64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief checks whether a JSON number fits into @a TargetType
|
||||||
|
@param[in] el a JSON number of either the signed or unsigned integer kind
|
||||||
|
@return whether @a el's value can be represented by @a TargetType without
|
||||||
|
wrapping, regardless of which of the two kinds it is stored as
|
||||||
|
*/
|
||||||
|
template<typename TargetType>
|
||||||
|
static bool bjdata_ndarray_value_in_range(const BasicJsonType& el)
|
||||||
|
{
|
||||||
|
return el.is_number_unsigned()
|
||||||
|
? value_in_range_of<TargetType>(el.template get<std::uint64_t>())
|
||||||
|
: value_in_range_of<TargetType>(el.template get<std::int64_t>());
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
@return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid
|
||||||
*/
|
*/
|
||||||
@@ -1644,10 +1692,43 @@ class binary_writer
|
|||||||
CharType dtype = it->second;
|
CharType dtype = it->second;
|
||||||
|
|
||||||
key = "_ArraySize_";
|
key = "_ArraySize_";
|
||||||
|
// the dimensions are written verbatim as the header length below, so a
|
||||||
|
// value that is not an array cannot produce a valid one: null emits 'Z'
|
||||||
|
// and an object emits '{', neither of which a reader accepts after '#'.
|
||||||
|
// Such an object is not a valid ndarray and falls back to a plain object.
|
||||||
|
if (!value.at(key).is_array())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t len = (value.at(key).empty() ? 0 : 1);
|
std::size_t len = (value.at(key).empty() ? 0 : 1);
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);
|
// a dimension is read as an unsigned value below, so anything that
|
||||||
|
// is not a non-negative integer is rejected: a non-integer entry
|
||||||
|
// would pun unrelated bytes as the dimension, and a negative one
|
||||||
|
// would wrap into a nonsensical length
|
||||||
|
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a dimension that does not fit into std::size_t, or a product that
|
||||||
|
// overflows it, would wrap around and could match the size of
|
||||||
|
// _ArrayData_ by accident; the resulting header announces an
|
||||||
|
// element count that no reader can honor (the binary reader rejects
|
||||||
|
// it with out_of_range.408), so encode as a plain object instead
|
||||||
|
const auto dim = el.template get<std::uint64_t>();
|
||||||
|
if (!value_in_range_of<std::size_t>(dim))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto dim_size = static_cast<std::size_t>(dim);
|
||||||
|
if (dim_size != 0 && len > (std::numeric_limits<std::size_t>::max)() / dim_size)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
len *= dim_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
key = "_ArrayData_";
|
key = "_ArrayData_";
|
||||||
@@ -1656,6 +1737,78 @@ class binary_writer
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// every element is written below as the number kind dtype names, so it
|
||||||
|
// has to actually be a number of that category: an element of any other
|
||||||
|
// type would reinterpret unrelated bytes, e.g. a string's heap pointer,
|
||||||
|
// as that number. Such an object falls back to a plain object encoding.
|
||||||
|
// dtype names the wire type, not the storage type: whether an integer
|
||||||
|
// is held as number_integer or number_unsigned depends on how the value
|
||||||
|
// was built (parsing stores non-negative integers as unsigned, the C++
|
||||||
|
// API stores int literals as signed), so both are accepted here and the
|
||||||
|
// writes below go through get<>, which reads the member that is active.
|
||||||
|
const bool ndarray_is_float = (dtype == 'd' || dtype == 'D');
|
||||||
|
for (const auto& el : value.at(key))
|
||||||
|
{
|
||||||
|
if (ndarray_is_float ? !el.is_number_float() : !el.is_number_integer())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// every element is cast to the (possibly narrower) C++ type matching
|
||||||
|
// dtype below; a value that does not fit that type would silently
|
||||||
|
// wrap (integers) or overflow to infinity (the "single" precision
|
||||||
|
// float) instead of being reported, so such an object falls back to
|
||||||
|
// a plain object encoding as well
|
||||||
|
for (const auto& el : value.at(key))
|
||||||
|
{
|
||||||
|
bool in_range = true;
|
||||||
|
switch (dtype)
|
||||||
|
{
|
||||||
|
case 'U':
|
||||||
|
case 'C':
|
||||||
|
case 'B':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
in_range = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
{
|
||||||
|
const auto dval = el.template get<double>();
|
||||||
|
in_range = !std::isfinite(dval) ||
|
||||||
|
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
|
dval <= static_cast<double>((std::numeric_limits<float>::max)()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// 'D' (double) already spans the full range of number_float_t
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!in_range)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oa->write_character('[');
|
oa->write_character('[');
|
||||||
oa->write_character('$');
|
oa->write_character('$');
|
||||||
oa->write_character(dtype);
|
oa->write_character(dtype);
|
||||||
@@ -1669,70 +1822,70 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true);
|
write_number(static_cast<std::uint8_t>(el.template get<std::uint64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'i')
|
else if (dtype == 'i')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true);
|
write_number(static_cast<std::int8_t>(el.template get<std::int64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'u')
|
else if (dtype == 'u')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true);
|
write_number(static_cast<std::uint16_t>(el.template get<std::uint64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'I')
|
else if (dtype == 'I')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true);
|
write_number(static_cast<std::int16_t>(el.template get<std::int64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'm')
|
else if (dtype == 'm')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true);
|
write_number(static_cast<std::uint32_t>(el.template get<std::uint64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'l')
|
else if (dtype == 'l')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true);
|
write_number(static_cast<std::int32_t>(el.template get<std::int64_t>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'M')
|
else if (dtype == 'M')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true);
|
write_number(el.template get<std::uint64_t>(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'L')
|
else if (dtype == 'L')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true);
|
write_number(el.template get<std::int64_t>(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'd')
|
else if (dtype == 'd')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<float>(el.m_data.m_value.number_float), true);
|
write_number(static_cast<float>(el.template get<double>()), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dtype == 'D')
|
else if (dtype == 'D')
|
||||||
{
|
{
|
||||||
for (const auto& el : value.at(key))
|
for (const auto& el : value.at(key))
|
||||||
{
|
{
|
||||||
write_number(static_cast<double>(el.m_data.m_value.number_float), true);
|
write_number(el.template get<double>(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1775,8 +1928,8 @@ class binary_writer
|
|||||||
void write_compact_float(const number_float_t n, detail::input_format_t format)
|
void write_compact_float(const number_float_t n, detail::input_format_t format)
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
if (!std::isfinite(n) || ((static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||||
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||||
@@ -1795,7 +1948,7 @@ class binary_writer
|
|||||||
write_number(n);
|
write_number(n);
|
||||||
}
|
}
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array> // array
|
#include <array> // array
|
||||||
|
#include <cmath> // isnan, ldexp, trunc
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string
|
#include <string> // string
|
||||||
|
#include <type_traits> // is_signed
|
||||||
|
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
@@ -114,5 +117,67 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare an integer with a floating point number without precision loss
|
||||||
|
|
||||||
|
Widening the integer to the floating point type loses precision beyond the
|
||||||
|
float's mantissa, which makes equality intransitive: both 2^63-2 and 2^63-1
|
||||||
|
round to 2^63, so each compares equal to that float while differing from each
|
||||||
|
other. Ordering built on that is not a strict weak ordering, so sorting such
|
||||||
|
values, or using them as keys in an ordered container, is undefined behavior.
|
||||||
|
|
||||||
|
Returns a value to be compared against zero with the original operator, which
|
||||||
|
reproduces the exact ordering. A NaN operand is returned as is, so comparing it
|
||||||
|
against zero keeps NaN's semantics: false for the relational operators and
|
||||||
|
unordered for `<=>`.
|
||||||
|
*/
|
||||||
|
template<typename IntegerType, typename FloatType>
|
||||||
|
FloatType compare_integer_with_float(const IntegerType i, const FloatType f) noexcept
|
||||||
|
{
|
||||||
|
const auto ordered = [](int c) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<FloatType>(c);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std::isnan(f))
|
||||||
|
{
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// values of IntegerType lie in [-bound, bound) when signed and in
|
||||||
|
// [0, bound) when unsigned; digits excludes the sign bit, so bound is a
|
||||||
|
// power of two that the float represents exactly
|
||||||
|
const FloatType bound = std::ldexp(static_cast<FloatType>(1), std::numeric_limits<IntegerType>::digits);
|
||||||
|
if (f >= bound)
|
||||||
|
{
|
||||||
|
return ordered(-1);
|
||||||
|
}
|
||||||
|
if (std::is_signed<IntegerType>::value ? (f < -bound) : (f < static_cast<FloatType>(0)))
|
||||||
|
{
|
||||||
|
return ordered(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// f is now within the integer's range, so truncating it is exact
|
||||||
|
const FloatType truncated = std::trunc(f);
|
||||||
|
const auto as_integer = static_cast<IntegerType>(truncated);
|
||||||
|
if (i != as_integer)
|
||||||
|
{
|
||||||
|
return ordered(i < as_integer ? -1 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the integer parts agree, so any fractional part decides
|
||||||
|
const FloatType fraction = f - truncated;
|
||||||
|
if (fraction > static_cast<FloatType>(0))
|
||||||
|
{
|
||||||
|
return ordered(-1);
|
||||||
|
}
|
||||||
|
if (fraction < static_cast<FloatType>(0))
|
||||||
|
{
|
||||||
|
return ordered(1);
|
||||||
|
}
|
||||||
|
return ordered(0);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
+338
-95
@@ -164,11 +164,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
detail::parser_callback_t<basic_json>cb = nullptr,
|
detail::parser_callback_t<basic_json>cb = nullptr,
|
||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false
|
const bool ignore_trailing_commas = false,
|
||||||
|
const bool discard_number_values = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
||||||
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -1335,21 +1336,26 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief serialization
|
/// @brief serialization
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/dump/
|
/// @sa https://json.nlohmann.me/api/basic_json/dump/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
string_t dump(const int indent = -1,
|
string_t dump(const int indent = -1,
|
||||||
const char indent_char = ' ',
|
const char indent_char = ' ',
|
||||||
const bool ensure_ascii = false,
|
const bool ensure_ascii = false,
|
||||||
const error_handler_t error_handler = error_handler_t::strict) const
|
const error_handler_t error_handler = error_handler_t::strict) const
|
||||||
{
|
{
|
||||||
string_t result;
|
string_t result;
|
||||||
serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);
|
detail::output_string_adapter<char, string_t> string_adapter(result);
|
||||||
|
|
||||||
if (indent >= 0)
|
if (indent >= 0)
|
||||||
{
|
{
|
||||||
s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
|
serializer s(string_adapter, indent_char,
|
||||||
|
true, ensure_ascii, static_cast<std::size_t>(indent), error_handler);
|
||||||
|
s.dump(*this);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
s.dump(*this, false, ensure_ascii, 0);
|
serializer s(string_adapter, indent_char,
|
||||||
|
false, ensure_ascii, 0, error_handler);
|
||||||
|
s.dump(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -1357,6 +1363,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return the type of the JSON value (explicit)
|
/// @brief return the type of the JSON value (explicit)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/type/
|
/// @sa https://json.nlohmann.me/api/basic_json/type/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr value_t type() const noexcept
|
constexpr value_t type() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type;
|
return m_data.m_type;
|
||||||
@@ -1364,6 +1371,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether type is primitive
|
/// @brief return whether type is primitive
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_primitive/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_primitive() const noexcept
|
constexpr bool is_primitive() const noexcept
|
||||||
{
|
{
|
||||||
return is_null() || is_string() || is_boolean() || is_number() || is_binary();
|
return is_null() || is_string() || is_boolean() || is_number() || is_binary();
|
||||||
@@ -1371,6 +1379,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether type is structured
|
/// @brief return whether type is structured
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_structured/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_structured() const noexcept
|
constexpr bool is_structured() const noexcept
|
||||||
{
|
{
|
||||||
return is_array() || is_object();
|
return is_array() || is_object();
|
||||||
@@ -1378,6 +1387,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is null
|
/// @brief return whether value is null
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_null/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_null/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_null() const noexcept
|
constexpr bool is_null() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::null;
|
return m_data.m_type == value_t::null;
|
||||||
@@ -1385,6 +1395,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is a boolean
|
/// @brief return whether value is a boolean
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_boolean/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_boolean() const noexcept
|
constexpr bool is_boolean() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::boolean;
|
return m_data.m_type == value_t::boolean;
|
||||||
@@ -1392,6 +1403,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is a number
|
/// @brief return whether value is a number
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_number/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_number/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_number() const noexcept
|
constexpr bool is_number() const noexcept
|
||||||
{
|
{
|
||||||
return is_number_integer() || is_number_float();
|
return is_number_integer() || is_number_float();
|
||||||
@@ -1399,6 +1411,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is an integer number
|
/// @brief return whether value is an integer number
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_number_integer() const noexcept
|
constexpr bool is_number_integer() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
|
return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;
|
||||||
@@ -1406,6 +1419,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is an unsigned integer number
|
/// @brief return whether value is an unsigned integer number
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_number_unsigned() const noexcept
|
constexpr bool is_number_unsigned() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::number_unsigned;
|
return m_data.m_type == value_t::number_unsigned;
|
||||||
@@ -1413,6 +1427,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is a floating-point number
|
/// @brief return whether value is a floating-point number
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_number_float/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_number_float() const noexcept
|
constexpr bool is_number_float() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::number_float;
|
return m_data.m_type == value_t::number_float;
|
||||||
@@ -1420,6 +1435,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is an object
|
/// @brief return whether value is an object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_object/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_object/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_object() const noexcept
|
constexpr bool is_object() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::object;
|
return m_data.m_type == value_t::object;
|
||||||
@@ -1427,6 +1443,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is an array
|
/// @brief return whether value is an array
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_array/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_array/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_array() const noexcept
|
constexpr bool is_array() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::array;
|
return m_data.m_type == value_t::array;
|
||||||
@@ -1434,6 +1451,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is a string
|
/// @brief return whether value is a string
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_string/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_string/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_string() const noexcept
|
constexpr bool is_string() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::string;
|
return m_data.m_type == value_t::string;
|
||||||
@@ -1441,6 +1459,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is a binary array
|
/// @brief return whether value is a binary array
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_binary/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_binary() const noexcept
|
constexpr bool is_binary() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::binary;
|
return m_data.m_type == value_t::binary;
|
||||||
@@ -1448,6 +1467,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return whether value is discarded
|
/// @brief return whether value is discarded
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
|
/// @sa https://json.nlohmann.me/api/basic_json/is_discarded/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
constexpr bool is_discarded() const noexcept
|
constexpr bool is_discarded() const noexcept
|
||||||
{
|
{
|
||||||
return m_data.m_type == value_t::discarded;
|
return m_data.m_type == value_t::discarded;
|
||||||
@@ -2779,6 +2799,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief returns the number of occurrences of a key in a JSON object
|
/// @brief returns the number of occurrences of a key in a JSON object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/count/
|
/// @sa https://json.nlohmann.me/api/basic_json/count/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
size_type count(const typename object_t::key_type& key) const
|
size_type count(const typename object_t::key_type& key) const
|
||||||
{
|
{
|
||||||
// return 0 for all nonobject types
|
// return 0 for all nonobject types
|
||||||
@@ -2789,6 +2810,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/count/
|
/// @sa https://json.nlohmann.me/api/basic_json/count/
|
||||||
template<class KeyType, detail::enable_if_t<
|
template<class KeyType, detail::enable_if_t<
|
||||||
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
size_type count(KeyType && key) const
|
size_type count(KeyType && key) const
|
||||||
{
|
{
|
||||||
// return 0 for all nonobject types
|
// return 0 for all nonobject types
|
||||||
@@ -2797,6 +2819,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief check the existence of an element in a JSON object
|
/// @brief check the existence of an element in a JSON object
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
bool contains(const typename object_t::key_type& key) const
|
bool contains(const typename object_t::key_type& key) const
|
||||||
{
|
{
|
||||||
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
|
return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();
|
||||||
@@ -2806,6 +2829,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
||||||
template<class KeyType, detail::enable_if_t<
|
template<class KeyType, detail::enable_if_t<
|
||||||
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
bool contains(KeyType && key) const
|
bool contains(KeyType && key) const
|
||||||
{
|
{
|
||||||
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
|
return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();
|
||||||
@@ -2813,12 +2837,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief check the existence of an element in a JSON object given a JSON pointer
|
/// @brief check the existence of an element in a JSON object given a JSON pointer
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
/// @sa https://json.nlohmann.me/api/basic_json/contains/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
bool contains(const json_pointer& ptr) const
|
bool contains(const json_pointer& ptr) const
|
||||||
{
|
{
|
||||||
return ptr.contains(this);
|
return ptr.contains(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)
|
||||||
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
|
bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const
|
||||||
{
|
{
|
||||||
@@ -2974,6 +3000,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief checks whether the container is empty.
|
/// @brief checks whether the container is empty.
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/empty/
|
/// @sa https://json.nlohmann.me/api/basic_json/empty/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
bool empty() const noexcept
|
bool empty() const noexcept
|
||||||
{
|
{
|
||||||
switch (m_data.m_type)
|
switch (m_data.m_type)
|
||||||
@@ -3013,6 +3040,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief returns the number of elements
|
/// @brief returns the number of elements
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/size/
|
/// @sa https://json.nlohmann.me/api/basic_json/size/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
size_type size() const noexcept
|
size_type size() const noexcept
|
||||||
{
|
{
|
||||||
switch (m_data.m_type)
|
switch (m_data.m_type)
|
||||||
@@ -3052,6 +3080,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief returns the maximum possible number of elements
|
/// @brief returns the maximum possible number of elements
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/max_size/
|
/// @sa https://json.nlohmann.me/api/basic_json/max_size/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
size_type max_size() const noexcept
|
size_type max_size() const noexcept
|
||||||
{
|
{
|
||||||
switch (m_data.m_type)
|
switch (m_data.m_type)
|
||||||
@@ -3425,6 +3454,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this));
|
JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container", this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// passed iterators must belong to arrays
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_array()))
|
||||||
|
{
|
||||||
|
JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to arrays", this));
|
||||||
|
}
|
||||||
|
|
||||||
// insert to array and return iterator
|
// insert to array and return iterator
|
||||||
return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);
|
||||||
}
|
}
|
||||||
@@ -3516,7 +3551,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
if (merge_objects && it.value().is_object())
|
if (merge_objects && it.value().is_object())
|
||||||
{
|
{
|
||||||
auto it2 = m_data.m_value.object->find(it.key());
|
auto it2 = m_data.m_value.object->find(it.key());
|
||||||
if (it2 != m_data.m_value.object->end())
|
// Only recurse when the existing value is itself an object.
|
||||||
|
// Otherwise overwrite, matching the documented "all other values
|
||||||
|
// are overwritten as usual" behavior (see #5402).
|
||||||
|
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||||
{
|
{
|
||||||
it2->second.update(it.value(), true);
|
it2->second.update(it.value(), true);
|
||||||
#if JSON_DIAGNOSTICS
|
#if JSON_DIAGNOSTICS
|
||||||
@@ -3570,6 +3608,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
{
|
{
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(*(m_data.m_value.array), other);
|
swap(*(m_data.m_value.array), other);
|
||||||
|
set_parents();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -3586,6 +3625,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
{
|
{
|
||||||
using std::swap;
|
using std::swap;
|
||||||
swap(*(m_data.m_value.object), other);
|
swap(*(m_data.m_value.object), other);
|
||||||
|
set_parents();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -3652,6 +3692,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
// note parentheses around operands are necessary; see
|
// note parentheses around operands are necessary; see
|
||||||
// https://github.com/nlohmann/json/issues/1530
|
// https://github.com/nlohmann/json/issues/1530
|
||||||
|
// Mixed signed/unsigned integer comparisons check whether the signed value
|
||||||
|
// is negative before casting. If it is, the comparison is performed with
|
||||||
|
// the fixed values -1 and 1, which preserves the ordering relationship
|
||||||
|
// because any negative signed value is smaller than any unsigned value.
|
||||||
|
// Otherwise, the non-negative signed value is cast to unsigned before the
|
||||||
|
// comparison to avoid wraparound.
|
||||||
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \
|
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \
|
||||||
const auto lhs_type = lhs.type(); \
|
const auto lhs_type = lhs.type(); \
|
||||||
const auto rhs_type = rhs.type(); \
|
const auto rhs_type = rhs.type(); \
|
||||||
@@ -3694,27 +3740,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \
|
else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \
|
||||||
{ \
|
{ \
|
||||||
return static_cast<number_float_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float; \
|
return (detail::compare_integer_with_float(lhs.m_data.m_value.number_integer, rhs.m_data.m_value.number_float)) op (static_cast<number_float_t>(0)); \
|
||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \
|
else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \
|
||||||
{ \
|
{ \
|
||||||
return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_integer); \
|
return (static_cast<number_float_t>(0)) op (detail::compare_integer_with_float(rhs.m_data.m_value.number_integer, lhs.m_data.m_value.number_float)); \
|
||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \
|
else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \
|
||||||
{ \
|
{ \
|
||||||
return static_cast<number_float_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float; \
|
return (detail::compare_integer_with_float(lhs.m_data.m_value.number_unsigned, rhs.m_data.m_value.number_float)) op (static_cast<number_float_t>(0)); \
|
||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \
|
else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \
|
||||||
{ \
|
{ \
|
||||||
return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned); \
|
return (static_cast<number_float_t>(0)) op (detail::compare_integer_with_float(rhs.m_data.m_value.number_unsigned, lhs.m_data.m_value.number_float)); \
|
||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \
|
else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \
|
||||||
{ \
|
{ \
|
||||||
return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \
|
return (rhs.m_data.m_value.number_integer < 0) \
|
||||||
|
? (number_integer_t(1) op number_integer_t(-1)) \
|
||||||
|
: (lhs.m_data.m_value.number_unsigned op static_cast<number_unsigned_t>(rhs.m_data.m_value.number_integer)); \
|
||||||
} \
|
} \
|
||||||
else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \
|
else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \
|
||||||
{ \
|
{ \
|
||||||
return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \
|
return (lhs.m_data.m_value.number_integer < 0) \
|
||||||
|
? (number_integer_t(-1) op number_integer_t(1)) \
|
||||||
|
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
|
||||||
} \
|
} \
|
||||||
else if(compares_unordered(lhs, rhs))\
|
else if(compares_unordered(lhs, rhs))\
|
||||||
{\
|
{\
|
||||||
@@ -3757,13 +3807,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
bool operator==(const_reference rhs) const noexcept
|
bool operator==(const_reference rhs) const noexcept
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3850,12 +3900,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
friend bool operator==(const_reference lhs, const_reference rhs) noexcept
|
friend bool operator==(const_reference lhs, const_reference rhs) noexcept
|
||||||
{
|
{
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic push
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
#pragma GCC diagnostic ignored "-Wfloat-equal"
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
#pragma GCC diagnostic pop
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4042,8 +4092,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
o.width(0);
|
o.width(0);
|
||||||
|
|
||||||
// do the actual serialization
|
// do the actual serialization
|
||||||
serializer s(detail::output_adapter<char>(o), o.fill());
|
detail::output_stream_adapter<char> stream_adapter(o);
|
||||||
s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));
|
serializer s(stream_adapter, o.fill(),
|
||||||
|
pretty_print, false, static_cast<std::size_t>(indentation));
|
||||||
|
s.dump(j);
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4083,12 +4135,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief deserialize from a pair of character iterators
|
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/parse/
|
/// @sa https://json.nlohmann.me/api/basic_json/parse/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json parse(IteratorType first,
|
static basic_json parse(IteratorType first,
|
||||||
IteratorType last,
|
SentinelType last,
|
||||||
parser_callback_t cb = nullptr,
|
parser_callback_t cb = nullptr,
|
||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
@@ -4115,21 +4168,24 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
/// @brief check if the input is valid JSON
|
/// @brief check if the input is valid JSON
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/accept/
|
/// @sa https://json.nlohmann.me/api/basic_json/accept/
|
||||||
template<typename InputType>
|
template<typename InputType>
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static bool accept(InputType&& i,
|
static bool accept(InputType&& i,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false)
|
const bool ignore_trailing_commas = false)
|
||||||
{
|
{
|
||||||
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
|
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief check if the input is valid JSON
|
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/accept/
|
/// @sa https://json.nlohmann.me/api/basic_json/accept/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
static bool accept(IteratorType first, IteratorType last,
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
|
static bool accept(IteratorType first, SentinelType last,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false)
|
const bool ignore_trailing_commas = false)
|
||||||
{
|
{
|
||||||
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
|
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
@@ -4138,7 +4194,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
const bool ignore_trailing_commas = false)
|
const bool ignore_trailing_commas = false)
|
||||||
{
|
{
|
||||||
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
|
return parser(i.get(), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events
|
||||||
@@ -4157,11 +4213,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief generate SAX events
|
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
|
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
|
||||||
template<class IteratorType, class SAX>
|
template<class IteratorType, class SAX, class SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_NON_NULL(3)
|
JSON_HEDLEY_NON_NULL(3)
|
||||||
static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,
|
static bool sax_parse(IteratorType first, SentinelType last, SAX* sax,
|
||||||
input_format_t format = input_format_t::json,
|
input_format_t format = input_format_t::json,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool ignore_comments = false,
|
const bool ignore_comments = false,
|
||||||
@@ -4223,6 +4280,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
/// @brief return the type as string
|
/// @brief return the type as string
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/type_name/
|
/// @sa https://json.nlohmann.me/api/basic_json/type_name/
|
||||||
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
JSON_HEDLEY_RETURNS_NON_NULL
|
JSON_HEDLEY_RETURNS_NON_NULL
|
||||||
const char* type_name() const noexcept
|
const char* type_name() const noexcept
|
||||||
{
|
{
|
||||||
@@ -4457,15 +4515,19 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in CBOR format
|
/// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
|
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json from_cbor(IteratorType first, IteratorType last,
|
static basic_json from_cbor(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true,
|
const bool allow_exceptions = true,
|
||||||
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
|
||||||
@@ -4473,8 +4535,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -4499,8 +4564,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in MessagePack format
|
/// @brief create a JSON value from an input in MessagePack format
|
||||||
@@ -4514,23 +4582,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in MessagePack format
|
/// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
|
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json from_msgpack(IteratorType first, IteratorType last,
|
static basic_json from_msgpack(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true)
|
const bool allow_exceptions = true)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -4553,8 +4628,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in UBJSON format
|
/// @brief create a JSON value from an input in UBJSON format
|
||||||
@@ -4568,23 +4646,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in UBJSON format
|
/// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
|
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json from_ubjson(IteratorType first, IteratorType last,
|
static basic_json from_ubjson(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true)
|
const bool allow_exceptions = true)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -4607,8 +4692,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in BJData format
|
/// @brief create a JSON value from an input in BJData format
|
||||||
@@ -4622,23 +4710,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in BJData format
|
/// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
|
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json from_bjdata(IteratorType first, IteratorType last,
|
static basic_json from_bjdata(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true)
|
const bool allow_exceptions = true)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in BSON format
|
/// @brief create a JSON value from an input in BSON format
|
||||||
@@ -4652,23 +4747,30 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
auto ia = detail::input_adapter(std::forward<InputType>(i));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief create a JSON value from an input in BSON format
|
/// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
|
||||||
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
|
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
|
||||||
template<typename IteratorType>
|
template<typename IteratorType, typename SentinelType = IteratorType,
|
||||||
|
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
|
||||||
JSON_HEDLEY_WARN_UNUSED_RESULT
|
JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
static basic_json from_bson(IteratorType first, IteratorType last,
|
static basic_json from_bson(IteratorType first, SentinelType last,
|
||||||
const bool strict = true,
|
const bool strict = true,
|
||||||
const bool allow_exceptions = true)
|
const bool allow_exceptions = true)
|
||||||
{
|
{
|
||||||
basic_json result;
|
basic_json result;
|
||||||
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
auto ia = detail::input_adapter(std::move(first), std::move(last));
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -4691,8 +4793,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
auto ia = i.get();
|
auto ia = i.get();
|
||||||
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
|
||||||
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
// NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)
|
||||||
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]
|
if (!binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict)) // cppcheck-suppress[accessMoved]
|
||||||
return res ? result : basic_json(value_t::discarded);
|
{
|
||||||
|
result = value_t::discarded;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
@@ -4918,6 +5023,36 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// note erase performs range check
|
// note erase performs range check
|
||||||
parent.erase(json_pointer::template array_index<basic_json_t>(last_path));
|
parent.erase(json_pointer::template array_index<basic_json_t>(last_path));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// the parent of a "remove" target must be an object or array
|
||||||
|
// (see #5396)
|
||||||
|
JSON_THROW(out_of_range::create(413, detail::concat("cannot remove value: the JSON Patch 'remove' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// RFC 6902 (section 4.4) forbids "from" from being a proper prefix
|
||||||
|
// of "path" for a "move" operation: a location cannot be moved into
|
||||||
|
// one of its own children. Compares reference tokens (already
|
||||||
|
// unescaped by json_pointer's parser) rather than the raw pointer
|
||||||
|
// strings, since a token may itself contain an escaped '/' or '~'
|
||||||
|
// that would defeat a naive string-prefix comparison. "from" equal
|
||||||
|
// to "path" is *not* a proper prefix and must return false.
|
||||||
|
const auto is_proper_prefix = [](const json_pointer & from, const json_pointer & to)
|
||||||
|
{
|
||||||
|
const auto from_size = from.reference_tokens.size();
|
||||||
|
if (from_size >= to.reference_tokens.size())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < from_size; ++i)
|
||||||
|
{
|
||||||
|
if (!(from.reference_tokens[i] == to.reference_tokens[i]))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// type check: top level value must be an array
|
// type check: top level value must be an array
|
||||||
@@ -4995,6 +5130,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
const auto from_path = get_value("move", "from", true).template get<string_t>();
|
||||||
json_pointer from_ptr(from_path);
|
json_pointer from_ptr(from_path);
|
||||||
|
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(is_proper_prefix(from_ptr, ptr)))
|
||||||
|
{
|
||||||
|
JSON_THROW(out_of_range::create(414, detail::concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result));
|
||||||
|
}
|
||||||
|
|
||||||
// the "from" location must exist - use at()
|
// the "from" location must exist - use at()
|
||||||
basic_json const v = result.at(from_ptr);
|
basic_json const v = result.at(from_ptr);
|
||||||
|
|
||||||
@@ -5107,19 +5247,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// We now reached the end of at least one array
|
// We now reached the end of at least one array
|
||||||
// in a second pass, traverse the remaining elements
|
// in a second pass, traverse the remaining elements
|
||||||
|
|
||||||
// remove my remaining elements
|
// remove my remaining elements, highest index first; appending
|
||||||
const auto end_index = static_cast<difference_type>(result.size());
|
// in that order avoids the quadratic reinsertion done before
|
||||||
while (i < source.size())
|
for (std::size_t j = source.size(); j > i; --j)
|
||||||
{
|
{
|
||||||
// add operations in reverse order to avoid invalid
|
result.push_back(object(
|
||||||
// indices
|
|
||||||
result.insert(result.begin() + end_index, object(
|
|
||||||
{
|
{
|
||||||
{"op", "remove"},
|
{"op", "remove"},
|
||||||
{"path", detail::concat<string_t>(path, '/', detail::to_string<string_t>(i))}
|
{"path", detail::concat<string_t>(path, '/', detail::to_string<string_t>(j - 1))}
|
||||||
}));
|
}));
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
|
i = source.size();
|
||||||
|
|
||||||
// add other remaining elements
|
// add other remaining elements
|
||||||
while (i < target.size())
|
while (i < target.size())
|
||||||
@@ -5138,21 +5276,96 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
|
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// first pass: traverse this object's elements
|
// first pass: record, for every source key, whether it is
|
||||||
|
// common to both objects (in source's iteration order) or
|
||||||
|
// was deleted (i.e., in source but not in target) -- this is
|
||||||
|
// a by-product of the target.find() call already needed to
|
||||||
|
// tell the two cases apart, so it adds no extra lookups. The
|
||||||
|
// "remove" ops themselves are emitted later, interleaved
|
||||||
|
// with the recursive per-key diffs in the fast path below,
|
||||||
|
// to match source's original iteration order (as the
|
||||||
|
// original, pre-reordering-aware implementation did) instead
|
||||||
|
// of grouping all removes before all recursive diffs.
|
||||||
|
std::vector<typename object_t::key_type> common_keys_source_order;
|
||||||
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
{
|
{
|
||||||
// escape the key name to be used in a JSON patch
|
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
|
||||||
|
|
||||||
if (target.find(it.key()) != target.end())
|
if (target.find(it.key()) != target.end())
|
||||||
{
|
{
|
||||||
// recursive call to compare object values at key it
|
common_keys_source_order.push_back(it.key());
|
||||||
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
}
|
||||||
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
}
|
||||||
|
|
||||||
|
// second pass: find keys that were added (i.e., in target but
|
||||||
|
// not in source), and record the keys common to both, in
|
||||||
|
// target's iteration order -- again a by-product of the
|
||||||
|
// source.find() call already needed to detect added keys. At
|
||||||
|
// the same time, determine whether every added key comes
|
||||||
|
// after every common key in target's order (a precondition
|
||||||
|
// for the fast path below, which only ever appends new keys
|
||||||
|
// at the very end): for an object_t whose iteration order is
|
||||||
|
// a pure function of the key set (e.g. the default std::map,
|
||||||
|
// which always iterates in sorted key order), the order
|
||||||
|
// check further below is always true and this whole
|
||||||
|
// mechanism is effectively a no-op; it only matters for a
|
||||||
|
// reorderable object_t such as the one backing `ordered_json`.
|
||||||
|
// patch ops for keys that were added (i.e., in target but not
|
||||||
|
// in source); built here so the fast path below can reuse
|
||||||
|
// them without a second source.find() per target key. Only
|
||||||
|
// used by the fast path -- the slow (reordering) path
|
||||||
|
// rebuilds "add" ops for every key itself.
|
||||||
|
std::vector<typename object_t::key_type> common_keys_target_order;
|
||||||
|
basic_json added_ops(value_t::array);
|
||||||
|
bool new_keys_form_suffix = true;
|
||||||
|
bool seen_new_key = false;
|
||||||
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (source.find(it.key()) == source.end())
|
||||||
|
{
|
||||||
|
seen_new_key = true;
|
||||||
|
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||||
|
added_ops.push_back(
|
||||||
|
{
|
||||||
|
{"op", "add"}, {"path", path_key},
|
||||||
|
{"value", it.value()}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// found a key that is not in o -> remove it
|
common_keys_target_order.push_back(it.key());
|
||||||
|
if (seen_new_key)
|
||||||
|
{
|
||||||
|
new_keys_form_suffix = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (common_keys_source_order == common_keys_target_order && new_keys_form_suffix)
|
||||||
|
{
|
||||||
|
// fast path: order of common keys already matches (or the
|
||||||
|
// object_t's iteration order does not depend on
|
||||||
|
// insertion history), so a plain per-key recursive diff
|
||||||
|
// is correct and minimal, as before. common_keys_source_order
|
||||||
|
// is, by construction, the subsequence of source's keys
|
||||||
|
// that are common to both objects, in source's iteration
|
||||||
|
// order -- so it can be walked in lockstep with `source`
|
||||||
|
// using a cheap key comparison instead of another lookup.
|
||||||
|
// Deleted keys (those source keys not in common_keys_source_order)
|
||||||
|
// are interleaved here too, in source's original order, to
|
||||||
|
// match the historical (pre-reordering-aware) output order.
|
||||||
|
auto common_it = common_keys_source_order.cbegin();
|
||||||
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
|
{
|
||||||
|
if (common_it != common_keys_source_order.cend() && it.key() == *common_it)
|
||||||
|
{
|
||||||
|
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||||
|
auto temp_diff = diff(it.value(), target[it.key()], path_key);
|
||||||
|
result.insert(result.end(), temp_diff.begin(), temp_diff.end());
|
||||||
|
++common_it;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// found a key that is not in target -> remove it
|
||||||
|
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||||
result.push_back(object(
|
result.push_back(object(
|
||||||
{
|
{
|
||||||
{"op", "remove"}, {"path", path_key}
|
{"op", "remove"}, {"path", path_key}
|
||||||
@@ -5160,12 +5373,42 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// second pass: traverse other object's elements
|
// append the "add" ops for brand-new keys collected above
|
||||||
|
// during the pass over target -- no second source.find()
|
||||||
|
// per target key needed
|
||||||
|
result.insert(result.end(), added_ops.begin(), added_ops.end());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// slow path: the common keys are in a different relative
|
||||||
|
// order in source and target (only possible for a
|
||||||
|
// reorderable object_t like ordered_map). Building a
|
||||||
|
// minimal reordering patch is a nontrivial (LCS-like)
|
||||||
|
// problem; instead, remove every source key -- both
|
||||||
|
// deleted keys (which must be removed regardless) and
|
||||||
|
// common keys (removed so they can be re-added in
|
||||||
|
// target's order) -- and re-add every key that should
|
||||||
|
// remain, with its final target value, in target's
|
||||||
|
// order. basic_json::patch()'s "add" operation on an
|
||||||
|
// object uses operator[], which appends at the end for a
|
||||||
|
// vector-backed insertion-ordered map when the key does
|
||||||
|
// not already exist -- so removing a key and then adding
|
||||||
|
// it moves it to the end, fixing its position.
|
||||||
|
for (auto it = source.cbegin(); it != source.cend(); ++it)
|
||||||
|
{
|
||||||
|
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||||
|
result.push_back(object(
|
||||||
|
{
|
||||||
|
{"op", "remove"}, {"path", path_key}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add every key that is either common (just removed
|
||||||
|
// above) or brand new, in target's iteration order, so
|
||||||
|
// that the final order after applying the patch matches
|
||||||
|
// target exactly
|
||||||
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
for (auto it = target.cbegin(); it != target.cend(); ++it)
|
||||||
{
|
{
|
||||||
if (source.find(it.key()) == source.end())
|
|
||||||
{
|
|
||||||
// found a key that is not in this -> add it
|
|
||||||
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));
|
||||||
result.push_back(
|
result.push_back(
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-1
@@ -17,7 +17,7 @@
|
|||||||
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN
|
#undef JSON_HEDLEY_CLANG_HAS_BUILTIN
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE
|
#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION
|
#undef JSON_HEDLEY_CLANG_HAS_EXTENSION
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_FEATURE
|
#undef JSON_HEDLEY_CLANG_HAS_FEATURE
|
||||||
#undef JSON_HEDLEY_CLANG_HAS_WARNING
|
#undef JSON_HEDLEY_CLANG_HAS_WARNING
|
||||||
@@ -108,7 +108,10 @@
|
|||||||
#undef JSON_HEDLEY_PELLES_VERSION_CHECK
|
#undef JSON_HEDLEY_PELLES_VERSION_CHECK
|
||||||
#undef JSON_HEDLEY_PGI_VERSION
|
#undef JSON_HEDLEY_PGI_VERSION
|
||||||
#undef JSON_HEDLEY_PGI_VERSION_CHECK
|
#undef JSON_HEDLEY_PGI_VERSION_CHECK
|
||||||
|
#undef JSON_HEDLEY_PRAGMA
|
||||||
#undef JSON_HEDLEY_PREDICT
|
#undef JSON_HEDLEY_PREDICT
|
||||||
|
#undef JSON_HEDLEY_PREDICT_FALSE
|
||||||
|
#undef JSON_HEDLEY_PREDICT_TRUE
|
||||||
#undef JSON_HEDLEY_PRINTF_FORMAT
|
#undef JSON_HEDLEY_PRINTF_FORMAT
|
||||||
#undef JSON_HEDLEY_PRIVATE
|
#undef JSON_HEDLEY_PRIVATE
|
||||||
#undef JSON_HEDLEY_PUBLIC
|
#undef JSON_HEDLEY_PUBLIC
|
||||||
|
|||||||
+3835
-739
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,9 @@ 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_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_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.")
|
||||||
@@ -125,6 +128,51 @@ json_test_set_test_options(test-unicode4 TEST_PROPERTIES TIMEOUT 3000)
|
|||||||
# add unit tests
|
# add unit tests
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
|
# Generate the leak checks for every JSON_HEDLEY_* macro defined in
|
||||||
|
# hedley.hpp; tests/src/unit-no-macro-leak.cpp #include-s the result after
|
||||||
|
# nlohmann/json.hpp (see issue #5408). Using the shared
|
||||||
|
# cmake/scripts/gen_hedley_undef_check.cmake script (also used by `make
|
||||||
|
# update_hedley_undef`) instead of a hand-maintained list of macro names
|
||||||
|
# means this test can never go stale after a future `make update_hedley`.
|
||||||
|
set(hedley_hpp "${PROJECT_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp")
|
||||||
|
set(hedley_undef_check_script "${PROJECT_SOURCE_DIR}/cmake/scripts/gen_hedley_undef_check.cmake")
|
||||||
|
set(hedley_undef_checks "${PROJECT_BINARY_DIR}/include/hedley_undef_checks.inc")
|
||||||
|
|
||||||
|
# Reconfigure whenever the vendored header or the generator script changes,
|
||||||
|
# so a `cmake --build` after `make update_hedley` does not silently keep a
|
||||||
|
# stale generated file around.
|
||||||
|
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||||
|
"${hedley_hpp}"
|
||||||
|
"${hedley_undef_check_script}")
|
||||||
|
|
||||||
|
# Generate once at configure time, so the very first build (before any
|
||||||
|
# custom-command build step has run) already has an up-to-date file.
|
||||||
|
execute_process(
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DHEDLEY_HPP=${hedley_hpp}"
|
||||||
|
"-DOUTPUT=${hedley_undef_checks}"
|
||||||
|
-DMODE=checks
|
||||||
|
-P "${hedley_undef_check_script}"
|
||||||
|
RESULT_VARIABLE hedley_undef_check_result
|
||||||
|
)
|
||||||
|
if(NOT hedley_undef_check_result EQUAL 0)
|
||||||
|
message(FATAL_ERROR "Failed to generate ${hedley_undef_checks}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Also (re)generate as a build step, so an incremental build after editing
|
||||||
|
# hedley.hpp without a full reconfigure still picks up the change.
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${hedley_undef_checks}"
|
||||||
|
COMMAND ${CMAKE_COMMAND}
|
||||||
|
"-DHEDLEY_HPP=${hedley_hpp}"
|
||||||
|
"-DOUTPUT=${hedley_undef_checks}"
|
||||||
|
-DMODE=checks
|
||||||
|
-P "${hedley_undef_check_script}"
|
||||||
|
DEPENDS "${hedley_hpp}" "${hedley_undef_check_script}"
|
||||||
|
COMMENT "Generating Hedley undef leak checks"
|
||||||
|
VERBATIM)
|
||||||
|
add_custom_target(generate_hedley_undef_checks DEPENDS "${hedley_undef_checks}")
|
||||||
|
|
||||||
if("${JSON_TestStandards}" STREQUAL "")
|
if("${JSON_TestStandards}" STREQUAL "")
|
||||||
set(test_cxx_standards 11 14 17 20 23)
|
set(test_cxx_standards 11 14 17 20 23)
|
||||||
unset(test_force)
|
unset(test_force)
|
||||||
@@ -149,6 +197,71 @@ if(test_force)
|
|||||||
endif()
|
endif()
|
||||||
message(STATUS "${msg}")
|
message(STATUS "${msg}")
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# optionally validate UTF-8 with simdutf (JSON_USE_SIMDUTF)
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
# The simdutf backend is opt-in and not vendored, so it is fetched here rather
|
||||||
|
# than being a checked-in dependency. Everything below hangs off test_main,
|
||||||
|
# whose usage requirements every test target inherits; the library target and
|
||||||
|
# the installed CMake package are deliberately left untouched.
|
||||||
|
if (JSON_TestSimdutf)
|
||||||
|
# simdutf requires C++17, both to compile itself and to be reachable from
|
||||||
|
# the library, which keeps its scalar validator below that. Find a tested
|
||||||
|
# standard that satisfies it.
|
||||||
|
set(simdutf_standard "")
|
||||||
|
foreach(cxx_standard ${test_cxx_standards})
|
||||||
|
if(NOT cxx_standard LESS 17 AND compiler_supports_cpp_${cxx_standard})
|
||||||
|
set(simdutf_standard ${cxx_standard})
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if("${simdutf_standard}" STREQUAL "")
|
||||||
|
# Building simdutf would fail outright without a C++17 compiler, and
|
||||||
|
# even with one it would go unused if no C++17-or-later standard is
|
||||||
|
# tested. Say so and fall back to the scalar validator rather than
|
||||||
|
# failing the build.
|
||||||
|
if(NOT compiler_supports_cpp_17)
|
||||||
|
set(simdutf_reason "the compiler does not support C++17")
|
||||||
|
else()
|
||||||
|
set(simdutf_reason "no tested standard is C++17 or later (testing ${msg_standards})")
|
||||||
|
endif()
|
||||||
|
message(WARNING
|
||||||
|
"JSON_TestSimdutf is enabled, but ${simdutf_reason}. simdutf requires C++17, so it "
|
||||||
|
"is not fetched and JSON_USE_SIMDUTF is not defined: the tests run against the "
|
||||||
|
"built-in scalar UTF-8 validator instead. Set JSON_TestStandards to include 17 or "
|
||||||
|
"later, or build with a compiler that supports C++17.")
|
||||||
|
else()
|
||||||
|
if (CMAKE_VERSION VERSION_LESS 3.18)
|
||||||
|
message(FATAL_ERROR "JSON_TestSimdutf requires CMake 3.18 or later (simdutf's minimum).")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
# simdutf builds its tests and tools by default, and its tests pull
|
||||||
|
# further dependencies of their own; only the library is needed here
|
||||||
|
set(SIMDUTF_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SIMDUTF_TOOLS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SIMDUTF_BENCHMARKS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(SIMDUTF_ICONV OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
FetchContent_Declare(simdutf
|
||||||
|
URL https://github.com/simdutf/simdutf/archive/refs/tags/v${JSON_SIMDUTF_VERSION}.tar.gz
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(simdutf)
|
||||||
|
|
||||||
|
target_compile_definitions(test_main PUBLIC JSON_USE_SIMDUTF)
|
||||||
|
target_link_libraries(test_main PUBLIC simdutf::simdutf)
|
||||||
|
|
||||||
|
# simdutf.h requires C++17; below that the library keeps its scalar
|
||||||
|
# validator, so any C++11/14 test targets exercise the fallback and the
|
||||||
|
# C++17-and-later ones exercise simdutf. Both must agree.
|
||||||
|
message(STATUS "UTF-8 validation delegated to simdutf ${JSON_SIMDUTF_VERSION} for C++17 and later (JSON_USE_SIMDUTF)")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# *DO* use json_test_set_test_options() above this line
|
# *DO* use json_test_set_test_options() above this line
|
||||||
|
|
||||||
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
json_test_should_build_32bit_test(json_32bit_test json_32bit_test_only "${JSON_32bitTest}")
|
||||||
@@ -163,6 +276,14 @@ foreach(file ${files})
|
|||||||
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
foreach(cxx_standard ${test_cxx_standards})
|
||||||
|
if(TARGET test-no-macro-leak_cpp${cxx_standard})
|
||||||
|
add_dependencies(test-no-macro-leak_cpp${cxx_standard} generate_hedley_undef_checks)
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
if(json_32bit_test_only)
|
if(json_32bit_test_only)
|
||||||
# Skip all other tests in this file
|
# Skip all other tests in this file
|
||||||
return()
|
return()
|
||||||
@@ -177,6 +298,24 @@ json_test_add_test_for(src/unit-comparison.cpp
|
|||||||
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# test the parser again with JSON_DIAGNOSTIC_POSITIONS enabled
|
||||||
|
json_test_set_test_options(test-class_parser_diagnostic_positions
|
||||||
|
COMPILE_DEFINITIONS JSON_DIAGNOSTIC_POSITIONS=1
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-class_parser.cpp
|
||||||
|
NAME test-class_parser_diagnostic_positions
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
|
||||||
|
# test diagnostic positions again without regular diagnostics (JSON pointer paths)
|
||||||
|
json_test_set_test_options(test-diagnostic-positions_only
|
||||||
|
COMPILE_DEFINITIONS JSON_DIAGNOSTICS=0
|
||||||
|
)
|
||||||
|
json_test_add_test_for(src/unit-diagnostic-positions.cpp
|
||||||
|
NAME test-diagnostic-positions_only
|
||||||
|
MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}
|
||||||
|
)
|
||||||
|
|
||||||
# *DO NOT* use json_test_set_test_options() below this line
|
# *DO NOT* use json_test_set_test_options() below this line
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|||||||
@@ -81,6 +81,44 @@ BENCHMARK_CAPTURE(ParseString, signed_ints, TEST_DATA_DIRECTORY "/regressi
|
|||||||
BENCHMARK_CAPTURE(ParseString, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
BENCHMARK_CAPTURE(ParseString, unsigned_ints, TEST_DATA_DIRECTORY "/regression/unsigned_ints.json");
|
||||||
BENCHMARK_CAPTURE(ParseString, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
BENCHMARK_CAPTURE(ParseString, small_signed_ints, TEST_DATA_DIRECTORY "/regression/small_signed_ints.json");
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// parse pretty-printed JSON from string
|
||||||
|
//
|
||||||
|
// Every file in the corpus above is minified or only lightly spaced, so none of
|
||||||
|
// them exercise the lexer's whitespace handling. Real-world JSON is frequently
|
||||||
|
// indented - configuration files, pretty-printed API responses, anything kept
|
||||||
|
// under version control - where insignificant whitespace can outweigh the data.
|
||||||
|
// Re-serializing a document with an indentation and parsing that keeps the
|
||||||
|
// content identical to the ParseString row above, so the pair isolates the cost
|
||||||
|
// of the whitespace alone.
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void ParseIndented(benchmark::State& state, const char* filename, int indent)
|
||||||
|
{
|
||||||
|
std::ifstream f(filename);
|
||||||
|
std::string str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
const std::string indented = json::parse(str).dump(indent);
|
||||||
|
|
||||||
|
while (state.KeepRunning())
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
auto* j = new json();
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
*j = json::parse(indented);
|
||||||
|
|
||||||
|
state.PauseTiming();
|
||||||
|
delete j;
|
||||||
|
state.ResumeTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * indented.size());
|
||||||
|
}
|
||||||
|
BENCHMARK_CAPTURE(ParseIndented, jeopardy / 4, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ParseIndented, canada / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ParseIndented, citm_catalog / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", 4);
|
||||||
|
BENCHMARK_CAPTURE(ParseIndented, twitter / 4, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", 4);
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
// serialize JSON
|
// serialize JSON
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -214,4 +252,323 @@ static void BinaryToCbor(benchmark::State& state)
|
|||||||
}
|
}
|
||||||
BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12);
|
BENCHMARK(BinaryToCbor)->RangeMultiplier(2)->Range(8, 8 << 12);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// parse binary formats
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Only MessagePack had a read benchmark (FromMsgpack above, left untouched so
|
||||||
|
// its numbers stay comparable across releases). The benchmarks below cover the
|
||||||
|
// other formats, and read from a contiguous buffer as well as from a FILE*:
|
||||||
|
// most callers pass a container, and the two adapters compile to different
|
||||||
|
// code. The test data repository ships JSON only, so the input for each is
|
||||||
|
// derived at setup time by serializing a parsed test file.
|
||||||
|
|
||||||
|
/// binary format to benchmark; the _optimized variants add UBJSON/BJData size
|
||||||
|
/// and type annotations, which the readers handle in a separate code path
|
||||||
|
enum class binary_format
|
||||||
|
{
|
||||||
|
cbor,
|
||||||
|
msgpack,
|
||||||
|
ubjson,
|
||||||
|
ubjson_optimized,
|
||||||
|
bjdata,
|
||||||
|
bjdata_optimized,
|
||||||
|
bson
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::vector<std::uint8_t> to_binary(const json& j, const binary_format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case binary_format::cbor:
|
||||||
|
return json::to_cbor(j);
|
||||||
|
case binary_format::msgpack:
|
||||||
|
return json::to_msgpack(j);
|
||||||
|
case binary_format::ubjson:
|
||||||
|
return json::to_ubjson(j);
|
||||||
|
case binary_format::ubjson_optimized:
|
||||||
|
return json::to_ubjson(j, true, true);
|
||||||
|
case binary_format::bjdata:
|
||||||
|
return json::to_bjdata(j);
|
||||||
|
case binary_format::bjdata_optimized:
|
||||||
|
return json::to_bjdata(j, true, true);
|
||||||
|
case binary_format::bson:
|
||||||
|
default:
|
||||||
|
return json::to_bson(j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json from_binary(const std::vector<std::uint8_t>& bytes, const binary_format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case binary_format::cbor:
|
||||||
|
return json::from_cbor(bytes);
|
||||||
|
case binary_format::msgpack:
|
||||||
|
return json::from_msgpack(bytes);
|
||||||
|
case binary_format::ubjson:
|
||||||
|
case binary_format::ubjson_optimized:
|
||||||
|
return json::from_ubjson(bytes);
|
||||||
|
case binary_format::bjdata:
|
||||||
|
case binary_format::bjdata_optimized:
|
||||||
|
return json::from_bjdata(bytes);
|
||||||
|
case binary_format::bson:
|
||||||
|
default:
|
||||||
|
return json::from_bson(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static json from_binary(std::FILE* file, const binary_format format)
|
||||||
|
{
|
||||||
|
switch (format)
|
||||||
|
{
|
||||||
|
case binary_format::cbor:
|
||||||
|
return json::from_cbor(file);
|
||||||
|
case binary_format::msgpack:
|
||||||
|
return json::from_msgpack(file);
|
||||||
|
case binary_format::ubjson:
|
||||||
|
case binary_format::ubjson_optimized:
|
||||||
|
return json::from_ubjson(file);
|
||||||
|
case binary_format::bjdata:
|
||||||
|
case binary_format::bjdata_optimized:
|
||||||
|
return json::from_bjdata(file);
|
||||||
|
case binary_format::bson:
|
||||||
|
default:
|
||||||
|
return json::from_bson(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief serialize a parsed test file to @a format
|
||||||
|
|
||||||
|
Returns an empty vector and marks the benchmark as skipped if the file cannot
|
||||||
|
be represented in the format, rather than letting the exception escape: BSON
|
||||||
|
requires an object at the top level, and several test files are arrays.
|
||||||
|
*/
|
||||||
|
static std::vector<std::uint8_t> binary_input(benchmark::State& state, const char* filename, const binary_format format)
|
||||||
|
{
|
||||||
|
std::ifstream f(filename);
|
||||||
|
std::string const str((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||||
|
const json j = json::parse(str);
|
||||||
|
|
||||||
|
if (format == binary_format::bson && !j.is_object())
|
||||||
|
{
|
||||||
|
state.SkipWithError("BSON requires an object at the top level");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return to_binary(j, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FromBinaryBuffer(benchmark::State& state, const char* filename, const binary_format format)
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
|
||||||
|
if (bytes.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
// the value is destroyed outside the timed section, because destroying
|
||||||
|
// a large DOM is not what this benchmark measures
|
||||||
|
state.PauseTiming();
|
||||||
|
auto* j = new json();
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
*j = from_binary(bytes, format);
|
||||||
|
|
||||||
|
state.PauseTiming();
|
||||||
|
delete j;
|
||||||
|
state.ResumeTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * bytes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / floats, TEST_DATA_DIRECTORY "/regression/floats.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, cbor / signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, msgpack / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / jeopardy, TEST_DATA_DIRECTORY "/jeopardy/jeopardy.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson_optimized);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, ubjson_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson_optimized);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bjdata_optimized);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bjdata_optimized / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata_optimized);
|
||||||
|
// BSON requires an object at the top level, so the array-rooted test files
|
||||||
|
// (jeopardy and the regression files) cannot be captured here
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::bson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json", binary_format::bson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryBuffer, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
||||||
|
|
||||||
|
static void FromBinaryFile(benchmark::State& state, const char* filename, const binary_format format)
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes = binary_input(state, filename, format);
|
||||||
|
if (bytes.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* tmp = "benchmark_input.bin";
|
||||||
|
std::ofstream o(tmp, std::ios::binary);
|
||||||
|
o.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
|
||||||
|
o.flush();
|
||||||
|
o.close();
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
auto* j = new json();
|
||||||
|
auto* file = std::fopen(tmp, "rb");
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
*j = from_binary(file, format);
|
||||||
|
|
||||||
|
state.PauseTiming();
|
||||||
|
std::fclose(file);
|
||||||
|
delete j;
|
||||||
|
state.ResumeTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * bytes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, cbor / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, cbor / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, ubjson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, bjdata / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bjdata);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryFile, bson / twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json", binary_format::bson);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// parse binary formats: value shapes
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// The test files above are wide and shallow, but the readers' cost is per
|
||||||
|
// container, so these cover the shapes that stress the container handling
|
||||||
|
// itself. Every shape is wrapped in an object so that BSON, which requires an
|
||||||
|
// object at the top level, measures the same value as the other formats.
|
||||||
|
|
||||||
|
/// deeply nested arrays: one container per level, no other work
|
||||||
|
static json make_nested()
|
||||||
|
{
|
||||||
|
json nested = json::array();
|
||||||
|
json* p = &nested;
|
||||||
|
for (std::size_t i = 1; i < 1000; ++i)
|
||||||
|
{
|
||||||
|
p->push_back(json::array());
|
||||||
|
p = &p->operator[](0);
|
||||||
|
}
|
||||||
|
|
||||||
|
json j = json::object();
|
||||||
|
j["data"] = std::move(nested);
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// many sibling containers: maximum container churn, minimum nesting
|
||||||
|
static json make_containers()
|
||||||
|
{
|
||||||
|
json data = json::array();
|
||||||
|
for (std::size_t i = 0; i < 100000; ++i)
|
||||||
|
{
|
||||||
|
data.push_back(json::array({1, 2}));
|
||||||
|
}
|
||||||
|
|
||||||
|
json j = json::object();
|
||||||
|
j["data"] = std::move(data);
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// one flat array of numbers: the scalar decoding path, which must not move
|
||||||
|
static json make_scalars()
|
||||||
|
{
|
||||||
|
json data = json::array();
|
||||||
|
for (std::size_t i = 0; i < 1000000; ++i)
|
||||||
|
{
|
||||||
|
data.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
json j = json::object();
|
||||||
|
j["data"] = std::move(data);
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FromBinaryShape(benchmark::State& state, json (*build)(), const binary_format format)
|
||||||
|
{
|
||||||
|
const std::vector<std::uint8_t> bytes = to_binary(build(), format);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
state.PauseTiming();
|
||||||
|
auto* j = new json();
|
||||||
|
state.ResumeTiming();
|
||||||
|
|
||||||
|
*j = from_binary(bytes, format);
|
||||||
|
|
||||||
|
state.PauseTiming();
|
||||||
|
delete j;
|
||||||
|
state.ResumeTiming();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * bytes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, nested / cbor, make_nested, binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, nested / msgpack, make_nested, binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, nested / ubjson, make_nested, binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, nested / bjdata, make_nested, binary_format::bjdata);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, nested / bson, make_nested, binary_format::bson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / cbor, make_containers, binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / msgpack, make_containers, binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson, make_containers, binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_containers, binary_format::ubjson_optimized);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
||||||
|
// BSON names every array element, so a large array measures key generation
|
||||||
|
// rather than scalar decoding and is left out here
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, scalars / msgpack, make_scalars, binary_format::msgpack);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, scalars / ubjson, make_scalars, binary_format::ubjson);
|
||||||
|
BENCHMARK_CAPTURE(FromBinaryShape, scalars / bjdata, make_scalars, binary_format::bjdata);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse an indefinite-length CBOR string
|
||||||
|
|
||||||
|
The writer never emits this form, so the input is assembled by hand: 0x7F
|
||||||
|
opens the string, each chunk is a one-character string, and 0xFF closes it.
|
||||||
|
*/
|
||||||
|
static void FromCborChunkedString(benchmark::State& state, const std::size_t chunks)
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> bytes;
|
||||||
|
bytes.reserve(2 * chunks + 2);
|
||||||
|
bytes.push_back(0x7F);
|
||||||
|
for (std::size_t i = 0; i < chunks; ++i)
|
||||||
|
{
|
||||||
|
bytes.push_back(0x61); // string of length 1
|
||||||
|
bytes.push_back(0x61); // 'a'
|
||||||
|
}
|
||||||
|
bytes.push_back(0xFF);
|
||||||
|
|
||||||
|
for (auto _ : state)
|
||||||
|
{
|
||||||
|
json j = json::from_cbor(bytes);
|
||||||
|
benchmark::DoNotOptimize(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.SetBytesProcessed(state.iterations() * bytes.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||||
|
|
||||||
BENCHMARK_MAIN();
|
BENCHMARK_MAIN();
|
||||||
|
|||||||
@@ -15,6 +15,15 @@
|
|||||||
namespace utils
|
namespace utils
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Some tests intentionally discard the [[nodiscard]]/JSON_HEDLEY_WARN_UNUSED_RESULT
|
||||||
|
// return value of a call they only make to exercise its side effects (e.g. checking
|
||||||
|
// that it does not throw). A plain (void) cast on the call expression does not
|
||||||
|
// suppress GCC's warning for functions using the GNU __attribute__((warn_unused_result))
|
||||||
|
// form (as opposed to the C++17 [[nodiscard]] attribute) -- passing the value into an
|
||||||
|
// ordinary function call does.
|
||||||
|
template<typename T>
|
||||||
|
inline void ignore_return_value(T&& /*unused*/) noexcept {}
|
||||||
|
|
||||||
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
||||||
{
|
{
|
||||||
std::ifstream file(filename, std::ios::binary);
|
std::ifstream file(filename, std::ios::binary);
|
||||||
@@ -30,4 +39,17 @@ inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
|
|||||||
return byte_vector;
|
return byte_vector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sentinel for istreambuf_iterator; compares != true until EOF is reached
|
||||||
|
// lets tests read a file directly via the new iterator+sentinel overloads
|
||||||
|
// instead of buffering the whole file into a vector first.
|
||||||
|
// Only the iterator-first direction (it != sentinel) is ever evaluated by
|
||||||
|
// the library's parse loop, so no reversed-order overload is needed.
|
||||||
|
struct istreambuf_sentinel
|
||||||
|
{
|
||||||
|
friend bool operator!=(const std::istreambuf_iterator<char>& it, const istreambuf_sentinel& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return it != std::istreambuf_iterator<char>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|||||||
@@ -132,3 +132,37 @@ TEST_CASE("BJData")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR")
|
||||||
|
{
|
||||||
|
SECTION("parse errors")
|
||||||
|
{
|
||||||
|
SECTION("array/map size larger than std::size_t")
|
||||||
|
{
|
||||||
|
// declared lengths do not fit in a 32-bit std::size_t and must not be truncated
|
||||||
|
std::vector<uint8_t> const varr = {0x9B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05};
|
||||||
|
std::vector<uint8_t> const vmap = {0xBB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(varr), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
|
||||||
|
CHECK(json::from_cbor(varr, true, false).is_discarded());
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vmap), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
|
||||||
|
CHECK(json::from_cbor(vmap, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array/map size equal to the indefinite-length sentinel")
|
||||||
|
{
|
||||||
|
// on 32-bit platforms a four-byte length of 0xFFFFFFFF aliases unknown_size()
|
||||||
|
std::vector<uint8_t> const varr = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
std::vector<uint8_t> const vmap = {0xBA, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(varr), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
|
||||||
|
CHECK(json::from_cbor(varr, true, false).is_discarded());
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vmap), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
|
||||||
|
CHECK(json::from_cbor(vmap, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+240
-3
@@ -1347,6 +1347,8 @@ TEST_CASE("BJData")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
|
||||||
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
|
||||||
|
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range);
|
||||||
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error);
|
||||||
}
|
}
|
||||||
@@ -2587,6 +2589,69 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
|
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray with data not matching _ArrayType_ is written as an object")
|
||||||
|
{
|
||||||
|
// A JData-annotated object is only serialized as an ndarray when
|
||||||
|
// its _ArrayData_ elements are actually stored as the number kind
|
||||||
|
// named by _ArrayType_. Otherwise the writer would read the wrong
|
||||||
|
// union member (e.g. a std::string's heap pointer as a uint64) and
|
||||||
|
// emit it, so such an object falls back to a plain object encoding
|
||||||
|
// that still round-trips.
|
||||||
|
|
||||||
|
// string data declared as a uint64 array
|
||||||
|
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {1}}, {"_ArrayData_", {"pointer"}}});
|
||||||
|
const auto out_str = json::to_bjdata(j_str);
|
||||||
|
CHECK(out_str.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_str) == j_str);
|
||||||
|
|
||||||
|
// integer data declared as a double array
|
||||||
|
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||||
|
const auto out_float = json::to_bjdata(j_float);
|
||||||
|
CHECK(out_float.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_float) == j_float);
|
||||||
|
|
||||||
|
// a non-integer shape entry is likewise not treated as an ndarray
|
||||||
|
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x"}}, {"_ArrayData_", {1}}});
|
||||||
|
const auto out_size = json::to_bjdata(j_size);
|
||||||
|
CHECK(out_size.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_size) == j_size);
|
||||||
|
|
||||||
|
// a negative shape entry is not a usable dimension either
|
||||||
|
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1],"_ArrayData_":[1]})");
|
||||||
|
const auto out_neg = json::to_bjdata(j_neg);
|
||||||
|
CHECK(out_neg.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_neg) == j_neg);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray parsed from text is written as a typed array")
|
||||||
|
{
|
||||||
|
// json::parse stores a non-negative integer as number_unsigned while
|
||||||
|
// the C++ API stores an int literal as number_integer, so _ArrayType_
|
||||||
|
// names the wire type rather than the storage. Both storages have to
|
||||||
|
// produce the same typed array for every type.
|
||||||
|
for (const char* type :
|
||||||
|
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char", "byte"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(type);
|
||||||
|
const std::string text = std::string(R"({"_ArrayType_":")") + type +
|
||||||
|
R"(","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
||||||
|
const auto from_text = json::to_bjdata(json::parse(text));
|
||||||
|
CHECK(from_text.at(0) == '[');
|
||||||
|
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", type}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}})));
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative values under a signed type behave the same way
|
||||||
|
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2],"_ArrayData_":[-5,7]})"));
|
||||||
|
CHECK(from_neg.at(0) == '[');
|
||||||
|
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-5, 7}}})));
|
||||||
|
|
||||||
|
// and so do the floating point types
|
||||||
|
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2],"_ArrayData_":[1.5,2.5]})"));
|
||||||
|
CHECK(from_float.at(0) == '[');
|
||||||
|
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 2.5}}})));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("optimized ndarray (type and vector-size as 1D array)")
|
SECTION("optimized ndarray (type and vector-size as 1D array)")
|
||||||
{
|
{
|
||||||
// create vector with two elements of the same type
|
// create vector with two elements of the same type
|
||||||
@@ -2665,6 +2730,99 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
|
CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
|
||||||
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray whose dimensions overflow stays as object")
|
||||||
|
{
|
||||||
|
// the product of the dimensions wraps around std::size_t to 0
|
||||||
|
// and so matches the size of the empty _ArrayData_; writing this
|
||||||
|
// as an ndarray would announce an element count no reader can
|
||||||
|
// honor, so it has to stay a plain object
|
||||||
|
json j_overflow = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {9223372036854775808ull, 2}}, {"_ArrayType_", "uint8"}});
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j_overflow), true, true) == j_overflow);
|
||||||
|
|
||||||
|
// a single dimension that does not fit into std::size_t is
|
||||||
|
// rejected for the same reason (only observable where
|
||||||
|
// std::size_t is narrower than 64 bit)
|
||||||
|
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
|
||||||
|
|
||||||
|
// a well-formed ndarray is still encoded as one
|
||||||
|
json j_ok = json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}});
|
||||||
|
CHECK(json::to_bjdata(j_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 1, 2, 3, 4, 5, 6}));
|
||||||
|
CHECK(json::from_bjdata(json::to_bjdata(j_ok), true, true) == j_ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray whose _ArraySize_ is not an array stays as object")
|
||||||
|
{
|
||||||
|
// the shape is written verbatim as the header length, so a
|
||||||
|
// value that is not an array cannot produce a valid one: null
|
||||||
|
// would emit 'Z' and an object '{', neither of which a reader
|
||||||
|
// accepts after '#'. Both have to stay plain objects.
|
||||||
|
json const j_null = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", nullptr}, {"_ArrayData_", json::array()}});
|
||||||
|
const auto out_null = json::to_bjdata(j_null);
|
||||||
|
CHECK(out_null.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_null) == j_null);
|
||||||
|
|
||||||
|
// an object shape passes the per-entry check by iterating its
|
||||||
|
// values rather than dimensions, so it needs rejecting too
|
||||||
|
json const j_obj = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {{"a", 1}}}, {"_ArrayData_", {1}}});
|
||||||
|
const auto out_obj = json::to_bjdata(j_obj);
|
||||||
|
CHECK(out_obj.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_obj) == j_obj);
|
||||||
|
|
||||||
|
// a scalar shape is not a dimension list either
|
||||||
|
json const j_num = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", 1}, {"_ArrayData_", {1}}});
|
||||||
|
const auto out_num = json::to_bjdata(j_num);
|
||||||
|
CHECK(out_num.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_num) == j_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ndarray with out-of-range _ArrayData_ elements stays as object")
|
||||||
|
{
|
||||||
|
// each element is cast to the (possibly narrower) C++ type
|
||||||
|
// named by _ArrayType_ before being written; a value that
|
||||||
|
// does not fit that type would silently wrap instead of
|
||||||
|
// being reported, so such an object falls back to a plain
|
||||||
|
// object encoding that still round-trips (see GitHub issue #5403)
|
||||||
|
|
||||||
|
// an unsigned element that does not fit uint8
|
||||||
|
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
|
||||||
|
const auto out_uint8 = json::to_bjdata(j_uint8);
|
||||||
|
CHECK(out_uint8.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_uint8) == j_uint8);
|
||||||
|
|
||||||
|
// a signed element that does not fit int8
|
||||||
|
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
|
||||||
|
const auto out_int8 = json::to_bjdata(j_int8);
|
||||||
|
CHECK(out_int8.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_int8) == j_int8);
|
||||||
|
|
||||||
|
// a negative element is likewise out of range for an
|
||||||
|
// unsigned _ArrayType_
|
||||||
|
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
|
||||||
|
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
|
||||||
|
CHECK(out_uint16_neg.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
|
||||||
|
|
||||||
|
// a double element that overflows to infinity when narrowed
|
||||||
|
// to the "single" (float) precision named by _ArrayType_
|
||||||
|
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||||
|
const auto out_single = json::to_bjdata(j_single);
|
||||||
|
CHECK(out_single.at(0) == '{');
|
||||||
|
CHECK(json::from_bjdata(out_single) == j_single);
|
||||||
|
|
||||||
|
// in-range boundary values still use the compact ndarray encoding
|
||||||
|
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
|
||||||
|
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
|
||||||
|
|
||||||
|
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
|
||||||
|
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
|
||||||
|
|
||||||
|
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
|
||||||
|
const auto out_single_ok = json::to_bjdata(j_single_ok);
|
||||||
|
CHECK(out_single_ok.at(0) == '[');
|
||||||
|
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2721,6 +2879,19 @@ TEST_CASE("BJData")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("negative length")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
|
||||||
|
std::vector<uint8_t> const vi = {'S', 'i', 0xFF};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(vi, true, false).is_discarded());
|
||||||
|
|
||||||
|
std::vector<uint8_t> const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(vl, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("parse bjdata markers in ubjson")
|
SECTION("parse bjdata markers in ubjson")
|
||||||
{
|
{
|
||||||
// create a single-character string for all number types
|
// create a single-character string for all number types
|
||||||
@@ -3164,8 +3335,10 @@ TEST_CASE("BJData")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR1), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vR1, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR1, true, false).is_discarded());
|
||||||
|
|
||||||
|
// a dimension vector that opens another one is rejected where the
|
||||||
|
// nested '[' is read, rather than after it has been descended into
|
||||||
std::vector<uint8_t> const vR2 = {'[', '$', 'i', '#', '[', '#', '[', 'i', 1, ']', ']', 1};
|
std::vector<uint8_t> const vR2 = {'[', '$', 'i', '#', '[', '#', '[', 'i', 1, ']', ']', 1};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR2), "[json.exception.parse_error.113] parse error at byte 11: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x5D", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR2), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vR2, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR2, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vR3 = {'[', '#', '[', 'i', '2', 'i', 2, ']'};
|
std::vector<uint8_t> const vR3 = {'[', '#', '[', 'i', '2', 'i', 2, ']'};
|
||||||
@@ -3173,7 +3346,7 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(vR3, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR3, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vR4 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', 1, ']', 1};
|
std::vector<uint8_t> const vR4 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', 1, ']', 1};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR4), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BJData number: unexpected end of input", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR4), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vR4, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR4, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vR5 = {'[', '$', 'i', '#', '[', '[', '[', ']', ']', ']'};
|
std::vector<uint8_t> const vR5 = {'[', '$', 'i', '#', '[', '[', '[', ']', ']', ']'};
|
||||||
@@ -3181,12 +3354,25 @@ TEST_CASE("BJData")
|
|||||||
CHECK(json::from_bjdata(vR5, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR5, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vR6 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
|
std::vector<uint8_t> const vR6 = {'[', '$', 'i', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR6), "[json.exception.parse_error.112] parse error at byte 14: syntax error while parsing BJData size: ndarray can not be recursive", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vR6), "[json.exception.parse_error.113] parse error at byte 9: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vR6, true, false).is_discarded());
|
CHECK(json::from_bjdata(vR6, true, false).is_discarded());
|
||||||
|
|
||||||
std::vector<uint8_t> const vH = {'[', 'H', '[', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
|
std::vector<uint8_t> const vH = {'[', 'H', '[', '#', '[', '$', 'i', '#', '[', 'i', '2', 'i', 2, ']'};
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vH), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vH), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
CHECK(json::from_bjdata(vH, true, false).is_discarded());
|
CHECK(json::from_bjdata(vH, true, false).is_discarded());
|
||||||
|
|
||||||
|
// Every "#[" of this chain used to open another dimension vector
|
||||||
|
// and cost several stack frames before anything was rejected, so a
|
||||||
|
// long enough chain crashed the process (see #5104). The nested
|
||||||
|
// vector is refused where it is read, so the length is irrelevant.
|
||||||
|
std::vector<uint8_t> vRdeep = {'['};
|
||||||
|
for (std::size_t i = 0; i < 100000; ++i)
|
||||||
|
{
|
||||||
|
vRdeep.push_back('#');
|
||||||
|
vRdeep.push_back('[');
|
||||||
|
}
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vRdeep), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing BJData size: ndarray dimensional vector is not allowed", json::parse_error&);
|
||||||
|
CHECK(json::from_bjdata(vRdeep, true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("objects")
|
SECTION("objects")
|
||||||
@@ -3699,6 +3885,15 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse BJData directly from a file using iterator and sentinel")
|
||||||
|
{
|
||||||
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bjdata";
|
||||||
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
const std::istreambuf_iterator<char> first(file);
|
||||||
|
const json parsed = json::from_bjdata(first, utils::istreambuf_sentinel{});
|
||||||
|
CHECK((parsed.is_object() || parsed.is_array()));
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("all BJData first bytes")
|
TEST_CASE("all BJData first bytes")
|
||||||
{
|
{
|
||||||
@@ -3735,6 +3930,48 @@ TEST_CASE("all BJData first bytes")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("BJData use_type requires use_size")
|
||||||
|
{
|
||||||
|
SECTION("non-empty object throws other_error.502")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}, {"b", 2}};
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true),
|
||||||
|
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||||
|
json::other_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("non-empty array throws other_error.502")
|
||||||
|
{
|
||||||
|
const json j = {1, 2, 3};
|
||||||
|
CHECK_THROWS_WITH_AS(json::to_bjdata(j, false, true),
|
||||||
|
"[json.exception.other_error.502] use_type requires use_size = true",
|
||||||
|
json::other_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("scalars do not throw with use_type=true, use_count=false")
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(42, false, true));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(3.14, false, true));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata("hello", false, true));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(true, false, true));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(nullptr, false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("empty containers do not throw with use_type=true, use_count=false")
|
||||||
|
{
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(json::array(), false, true));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(json::object(), false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("valid combinations on non-empty containers")
|
||||||
|
{
|
||||||
|
const json j = {{"a", 1}, {"b", 2}};
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(j, false, false));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(j, true, false));
|
||||||
|
CHECK_NOTHROW(json::to_bjdata(j, true, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BJData roundtrips" * doctest::skip())
|
TEST_CASE("BJData roundtrips" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("input from self-generated BJData files")
|
SECTION("input from self-generated BJData files")
|
||||||
|
|||||||
@@ -11,12 +11,83 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// a binary container that reports a size beyond INT32_MAX without allocating
|
||||||
|
// that much memory, so the BSON length overflow can be tested cheaply
|
||||||
|
class huge_binary_t : public std::vector<std::uint8_t>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using std::vector<std::uint8_t>::vector;
|
||||||
|
|
||||||
|
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
||||||
|
{
|
||||||
|
// one byte more than the BSON length field can represent
|
||||||
|
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using huge_binary_json = nlohmann::basic_json <
|
||||||
|
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
||||||
|
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
|
||||||
|
|
||||||
|
// a string type that can be made to report a size beyond INT32_MAX without
|
||||||
|
// allocating that much memory, so BSON length overflow can be tested for
|
||||||
|
// strings and (embedded) documents as well, following the same idea as
|
||||||
|
// huge_binary_t.
|
||||||
|
//
|
||||||
|
// Unlike huge_binary_t (which is only ever used as the BSON *value* type),
|
||||||
|
// this type doubles as basic_json's StringType and is therefore also used
|
||||||
|
// for *object keys* (e.g. "s" or "nested" below). Only the designated test
|
||||||
|
// value is meant to lie about its size - if every huge_string_t (including
|
||||||
|
// keys) reported a huge size, the running totals computed while walking the
|
||||||
|
// BSON document (see calc_bson_object_size & friends in binary_writer.hpp)
|
||||||
|
// would need more than 32 bits, and on platforms where std::size_t is only
|
||||||
|
// 32 bits wide that arithmetic would silently wrap around, producing wrong
|
||||||
|
// (or even unguarded) lengths. The fake size is therefore opt-in via
|
||||||
|
// as_huge(), and plain strings - in particular object keys - keep reporting
|
||||||
|
// their real, small size.
|
||||||
|
class huge_string_t : public std::string
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using std::string::string;
|
||||||
|
huge_string_t(const std::string& s) : std::string(s) {} // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
|
||||||
|
|
||||||
|
// returns a copy of @a s whose size() pretends to be huge
|
||||||
|
static huge_string_t as_huge(const std::string& s)
|
||||||
|
{
|
||||||
|
huge_string_t result(s);
|
||||||
|
result.pretend_huge = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type size() const noexcept
|
||||||
|
{
|
||||||
|
if (pretend_huge)
|
||||||
|
{
|
||||||
|
// one byte more than the BSON length field can represent
|
||||||
|
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
|
||||||
|
}
|
||||||
|
return std::string::size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool pretend_huge = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
using huge_string_json = nlohmann::basic_json <
|
||||||
|
std::map, std::vector, huge_string_t, bool, std::int64_t, std::uint64_t,
|
||||||
|
double, std::allocator, nlohmann::adl_serializer, std::vector<std::uint8_t>, void >;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("BSON")
|
TEST_CASE("BSON")
|
||||||
{
|
{
|
||||||
SECTION("individual values not supported")
|
SECTION("individual values not supported")
|
||||||
@@ -80,6 +151,40 @@ TEST_CASE("BSON")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
||||||
|
{
|
||||||
|
// out_of_range.412 is thrown from a single shared helper
|
||||||
|
// (to_bson_length) that guards the BSON length fields of binary
|
||||||
|
// values, strings, and (embedded) documents alike
|
||||||
|
SECTION("binary")
|
||||||
|
{
|
||||||
|
huge_binary_json j;
|
||||||
|
j["b"] = huge_binary_json::binary(huge_binary_t{});
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("string")
|
||||||
|
{
|
||||||
|
huge_string_json j;
|
||||||
|
j["s"] = huge_string_t::as_huge("value");
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("document")
|
||||||
|
{
|
||||||
|
// an oversized string nested one level deep makes the
|
||||||
|
// *embedded* document's own length exceed INT32_MAX as well
|
||||||
|
huge_string_json nested;
|
||||||
|
nested["s"] = huge_string_t::as_huge("value");
|
||||||
|
huge_string_json j;
|
||||||
|
j["nested"] = nested;
|
||||||
|
|
||||||
|
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483674 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("string length must be at least 1")
|
SECTION("string length must be at least 1")
|
||||||
{
|
{
|
||||||
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
||||||
@@ -162,6 +267,23 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("non-empty object with bool from a non-0/1 byte (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): any non-zero byte
|
||||||
|
// is accepted as `true`, not just 0x01
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x0D, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x08, // entry: boolean
|
||||||
|
'e', 'n', 't', 'r', 'y', '\x00',
|
||||||
|
0x02, // value = 0x02 (neither 0x00 nor 0x01)
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected = { { "entry", true } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-empty object with double")
|
SECTION("non-empty object with double")
|
||||||
{
|
{
|
||||||
json const j =
|
json const j =
|
||||||
@@ -468,6 +590,29 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("array elements with non-conforming keys (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): BSON array element
|
||||||
|
// keys are not checked against the required decimal sequence
|
||||||
|
// "0", "1", "2", ... - elements are taken in encoded order
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x26, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x04, 'e', 'n', 't', 'r', 'y', '\x00', // entry: embedded array
|
||||||
|
|
||||||
|
0x1A, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x10, '5', 0x00, 0x0A, 0x00, 0x00, 0x00, // key "5" (bogus) -> 10
|
||||||
|
0x10, 'x', 0x00, 0x14, 0x00, 0x00, 0x00, // key "x" (non-numeric) -> 20
|
||||||
|
0x10, '1', 0x00, 0x1E, 0x00, 0x00, 0x00, // key "1" (out of order) -> 30
|
||||||
|
0x00, // end marker (embedded array)
|
||||||
|
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const json expected = { { "entry", json::array({10, 20, 30}) } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-empty object with binary member")
|
SECTION("non-empty object with binary member")
|
||||||
{
|
{
|
||||||
const size_t N = 10;
|
const size_t N = 10;
|
||||||
@@ -498,6 +643,41 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("non-empty object with binary member without subtype")
|
||||||
|
{
|
||||||
|
const size_t N = 10;
|
||||||
|
const auto s = std::vector<std::uint8_t>(N, 'x');
|
||||||
|
json const j =
|
||||||
|
{
|
||||||
|
{ "entry", json::binary(s) }
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK(!j.at("entry").get_binary().has_subtype());
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> const expected =
|
||||||
|
{
|
||||||
|
0x1B, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, // entry: binary
|
||||||
|
'e', 'n', 't', 'r', 'y', '\x00',
|
||||||
|
|
||||||
|
0x0A, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x00, // Generic binary subtype
|
||||||
|
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
|
||||||
|
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto result = json::to_bson(j);
|
||||||
|
CHECK(result == expected);
|
||||||
|
|
||||||
|
// roundtrip adds the generic binary subtype
|
||||||
|
const auto roundtrip = json::from_bson(result);
|
||||||
|
CHECK(roundtrip != j);
|
||||||
|
CHECK(roundtrip.at("entry").get_binary().has_subtype());
|
||||||
|
CHECK(roundtrip.at("entry").get_binary().subtype() == 0);
|
||||||
|
CHECK(json::from_bson(result, true, false) == roundtrip);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("non-empty object with binary member with subtype")
|
SECTION("non-empty object with binary member with subtype")
|
||||||
{
|
{
|
||||||
// an MD5 hash
|
// an MD5 hash
|
||||||
@@ -528,6 +708,31 @@ TEST_CASE("BSON")
|
|||||||
CHECK(json::from_bson(result, true, false) == j);
|
CHECK(json::from_bson(result, true, false) == j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("binary member with subtype 0x02 (old binary) keeps its inner length prefix (lenient parsing)")
|
||||||
|
{
|
||||||
|
// documented lenient behavior (see gh-5333): the payload for
|
||||||
|
// binary subtype 0x02 ("old binary") is returned as-is,
|
||||||
|
// including its own inner 4-byte length prefix; it is not
|
||||||
|
// stripped or reinterpreted
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x17, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, 'e', 'n', 't', 'r', 'y', '\x00', // entry: binary
|
||||||
|
|
||||||
|
0x06, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||||
|
0x02, // "old binary" subtype
|
||||||
|
0x02, 0x00, 0x00, 0x00, // inner length prefix (part of the old-binary payload)
|
||||||
|
0x68, 0x69, // payload ('h', 'i')
|
||||||
|
|
||||||
|
0x00 // end marker
|
||||||
|
};
|
||||||
|
|
||||||
|
// the inner length prefix is part of the (unmodified) payload
|
||||||
|
const std::vector<std::uint8_t> expected_payload = {0x02, 0x00, 0x00, 0x00, 0x68, 0x69};
|
||||||
|
const json expected = { { "entry", json::binary(expected_payload, 0x02) } };
|
||||||
|
CHECK(json::from_bson(input) == expected);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Some more complex document")
|
SECTION("Some more complex document")
|
||||||
{
|
{
|
||||||
json const j =
|
json const j =
|
||||||
@@ -794,6 +999,41 @@ TEST_CASE("Incomplete BSON Input")
|
|||||||
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
|
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Incomplete BSON Input 5")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const incomplete_bson =
|
||||||
|
{
|
||||||
|
0x09, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x08, // entry: boolean
|
||||||
|
'b', '\x00' // key, unexpected EOF before the value
|
||||||
|
};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
|
||||||
|
|
||||||
|
SaxCountdown scp(0);
|
||||||
|
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("Incomplete BSON Input 6")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const incomplete_bson =
|
||||||
|
{
|
||||||
|
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||||
|
0x05, // entry: binary
|
||||||
|
'b', '\x00', // key
|
||||||
|
0x00, 0x00, 0x00, 0x00 // length, unexpected EOF before the subtype
|
||||||
|
};
|
||||||
|
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
|
||||||
|
|
||||||
|
SaxCountdown scp(0);
|
||||||
|
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("Improve coverage")
|
SECTION("Improve coverage")
|
||||||
{
|
{
|
||||||
SECTION("key")
|
SECTION("key")
|
||||||
@@ -854,6 +1094,147 @@ TEST_CASE("Unsupported BSON input")
|
|||||||
CHECK(!json::sax_parse(bson, &scp, json::input_format_t::bson));
|
CHECK(!json::sax_parse(bson, &scp, json::input_format_t::bson));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BSON document size mismatch")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
|
||||||
|
SECTION("top-level document declaring more bytes than it contains")
|
||||||
|
{
|
||||||
|
// empty object, but the length prefix claims 6 bytes instead of 5
|
||||||
|
std::vector<std::uint8_t> const input = {0x06, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("top-level document with a negative size")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size -1 does not match the number of bytes read (5)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("embedded document whose size disagrees with its terminator")
|
||||||
|
{
|
||||||
|
// the embedded document "d" declares 0x7FFFFFFF bytes but its 0x00
|
||||||
|
// terminator falls right after {"a":null}; the length prefix would
|
||||||
|
// otherwise let the following "h" element be read as a member of the
|
||||||
|
// enclosing document instead of "d"
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x00, 0x00, 0x00, 0x00, // outer size
|
||||||
|
0x03, 'd', 0x00, // entry: embedded document "d"
|
||||||
|
0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF
|
||||||
|
0x0A, 'a', 0x00, // entry: null "a"
|
||||||
|
0x00, // embedded end marker
|
||||||
|
0x08, 'h', 0x00, 0x01, // entry: bool "h" = true
|
||||||
|
0x00 // outer end marker
|
||||||
|
};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 2147483647 does not match the number of bytes read (8)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("embedded array whose size disagrees with its terminator")
|
||||||
|
{
|
||||||
|
// array [42] is 12 bytes, but the length prefix claims 13
|
||||||
|
std::vector<std::uint8_t> const input =
|
||||||
|
{
|
||||||
|
0x00, 0x00, 0x00, 0x00, // outer size
|
||||||
|
0x04, 'a', 0x00, // entry: array "a"
|
||||||
|
0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12)
|
||||||
|
0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42
|
||||||
|
0x00, // array end marker
|
||||||
|
0x00 // outer end marker
|
||||||
|
};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 13 does not match the number of bytes read (12)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BSON nesting does not consume the call stack")
|
||||||
|
{
|
||||||
|
// An embedded document or array used to be read by calling back into the
|
||||||
|
// document reader, so the native call stack grew with the nesting depth of
|
||||||
|
// the input (#5104). The open documents are kept on a heap stack now.
|
||||||
|
//
|
||||||
|
// Deeply nested values must not be compared, copied or dumped here: those
|
||||||
|
// operations are still recursive and would reintroduce the crash.
|
||||||
|
|
||||||
|
// A document nested deeply enough to have crashed. The bytes are built
|
||||||
|
// here rather than with to_bson(), because the writer still recurses once
|
||||||
|
// per level and would overflow the stack before the reader is ever
|
||||||
|
// reached. Every level is
|
||||||
|
// <int32 size> 0x03 'a' 0x00 <inner document> 0x00
|
||||||
|
// so a level is eight bytes larger than the one it holds, and the sizes
|
||||||
|
// can be filled in from the outside in.
|
||||||
|
const std::size_t depth = 30000;
|
||||||
|
std::vector<uint8_t> input;
|
||||||
|
input.reserve(5 + (8 * depth));
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
const auto size = static_cast<std::uint32_t>(5 + (8 * (depth - i)));
|
||||||
|
input.push_back(static_cast<uint8_t>(size & 0xFF));
|
||||||
|
input.push_back(static_cast<uint8_t>((size >> 8) & 0xFF));
|
||||||
|
input.push_back(static_cast<uint8_t>((size >> 16) & 0xFF));
|
||||||
|
input.push_back(static_cast<uint8_t>((size >> 24) & 0xFF));
|
||||||
|
input.push_back(0x03); // embedded document
|
||||||
|
input.push_back('a');
|
||||||
|
input.push_back(0x00);
|
||||||
|
}
|
||||||
|
// the innermost document is empty, then one terminator closes each level
|
||||||
|
input.insert(input.end(), {0x05, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
input.insert(input.end(), depth, 0x00);
|
||||||
|
|
||||||
|
SECTION("a well-formed deep document is read through the SAX interface")
|
||||||
|
{
|
||||||
|
SaxCountdown accept_all(1000000);
|
||||||
|
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::bson));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a well-formed deep document is read into a value")
|
||||||
|
{
|
||||||
|
json j = json::from_bson(input);
|
||||||
|
|
||||||
|
// walked rather than compared: comparing, copying or dumping a value
|
||||||
|
// this deep is still recursive
|
||||||
|
std::size_t measured = 0;
|
||||||
|
const json* q = &j;
|
||||||
|
while (q->is_object() && !q->empty())
|
||||||
|
{
|
||||||
|
q = &q->begin().value();
|
||||||
|
++measured;
|
||||||
|
}
|
||||||
|
CHECK(measured == depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("embedded documents and arrays are still read the same way")
|
||||||
|
{
|
||||||
|
const json values = {{"a", {{"b", {{"c", 1}}}}}};
|
||||||
|
CHECK(json::from_bson(json::to_bson(values)) == values);
|
||||||
|
|
||||||
|
const json array = {{"a", {1, 2, 3}}};
|
||||||
|
CHECK(json::from_bson(json::to_bson(array)) == array);
|
||||||
|
|
||||||
|
const json mixed = {{"a", {json{{"x", 1}}, json{{"y", 2}}}}};
|
||||||
|
CHECK(json::from_bson(json::to_bson(mixed)) == mixed);
|
||||||
|
|
||||||
|
CHECK(json::from_bson(json::to_bson(json::object())) == json::object());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a size that does not match is still reported per document")
|
||||||
|
{
|
||||||
|
// the embedded document claims one byte too many
|
||||||
|
std::vector<uint8_t> const bad =
|
||||||
|
{
|
||||||
|
0x15, 0x00, 0x00, 0x00, 0x03, 'a', 0x00,
|
||||||
|
0x0D, 0x00, 0x00, 0x00, 0x08, 'b', 0x00, 0x01, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_AS(_ = json::from_bson(bad), json::parse_error&);
|
||||||
|
CHECK(json::from_bson(bad, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON numerical data")
|
TEST_CASE("BSON numerical data")
|
||||||
{
|
{
|
||||||
SECTION("number")
|
SECTION("number")
|
||||||
@@ -1208,6 +1589,19 @@ TEST_CASE("BSON numerical data")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse BSON directly from a file using iterator and sentinel")
|
||||||
|
{
|
||||||
|
std::string const filename = TEST_DATA_DIRECTORY "/json.org/1.json";
|
||||||
|
|
||||||
|
std::ifstream f_json(filename);
|
||||||
|
const json expected = json::parse(f_json);
|
||||||
|
|
||||||
|
std::ifstream file(filename + ".bson", std::ios::binary);
|
||||||
|
const std::istreambuf_iterator<char> first(file);
|
||||||
|
const json parsed = json::from_bson(first, utils::istreambuf_sentinel{});
|
||||||
|
CHECK(parsed == expected);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON roundtrips" * doctest::skip())
|
TEST_CASE("BSON roundtrips" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("reference files")
|
SECTION("reference files")
|
||||||
|
|||||||
+198
-3
@@ -1921,6 +1921,15 @@ TEST_CASE("single CBOR roundtrip")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Parse CBOR directly from a file using iterator and sentinel")
|
||||||
|
{
|
||||||
|
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.cbor";
|
||||||
|
std::ifstream file(filename, std::ios::binary);
|
||||||
|
const std::istreambuf_iterator<char> first(file);
|
||||||
|
const json parsed = json::from_cbor(first, utils::istreambuf_sentinel{});
|
||||||
|
CHECK((parsed.is_object() || parsed.is_array()));
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(JSON_NOEXCEPTION)
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
TEST_CASE("CBOR regressions")
|
TEST_CASE("CBOR regressions")
|
||||||
{
|
{
|
||||||
@@ -1990,6 +1999,181 @@ TEST_CASE("CBOR regressions")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
TEST_CASE("CBOR definite length equal to the indefinite-length sentinel")
|
||||||
|
{
|
||||||
|
// A definite-length array or map whose declared element count equals the
|
||||||
|
// reserved unknown_size() sentinel (SIZE_MAX) must be rejected. Otherwise
|
||||||
|
// it is read as an indefinite-length container and the following bytes are
|
||||||
|
// silently accepted instead of the (impossible) count being reported.
|
||||||
|
json _;
|
||||||
|
|
||||||
|
SECTION("array")
|
||||||
|
{
|
||||||
|
// 0x9B: array with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
|
||||||
|
const std::vector<uint8_t> input = {0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x02, 0xFF};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("map")
|
||||||
|
{
|
||||||
|
// 0xBB: map with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
|
||||||
|
const std::vector<uint8_t> input = {0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0x61, 0x01, 0xFF};
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("indefinite-length containers are unaffected")
|
||||||
|
{
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01, 0x02, 0xFF})) == json({1, 2}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0x01, 0xFF})) == json({{"a", 1}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("ordinary four-byte length containers are unaffected")
|
||||||
|
{
|
||||||
|
// 0x9A/0xBA carry a four-byte length; a normal count still parses
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9A, 0x00, 0x00, 0x00, 0x02, 0x01, 0x02})) == json({1, 2}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBA, 0x00, 0x00, 0x00, 0x01, 0x61, 0x61, 0x01})) == json({{"a", 1}}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR nesting does not consume the call stack")
|
||||||
|
{
|
||||||
|
// Containers used to be read by calling back into the value reader once
|
||||||
|
// per element, and a tag by calling it for the tagged value, so the native
|
||||||
|
// call stack grew with the nesting depth of the input. Each of the three
|
||||||
|
// costs a single byte to encode -- 0x9F, 0x81 and 0xC2 -- so a payload of
|
||||||
|
// repeated bytes crashed the process (#5104). The containers are kept on a
|
||||||
|
// heap stack now, and a tag is read in a loop.
|
||||||
|
//
|
||||||
|
// Deeply nested values must not be compared, copied or dumped here: those
|
||||||
|
// operations are still recursive and would reintroduce the crash.
|
||||||
|
json _;
|
||||||
|
|
||||||
|
SECTION("indefinite-length containers")
|
||||||
|
{
|
||||||
|
const std::vector<uint8_t> input(500000, 0x9F);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("definite-length containers")
|
||||||
|
{
|
||||||
|
const std::vector<uint8_t> input(500000, 0x81);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("tags")
|
||||||
|
{
|
||||||
|
// a tag is not a value of its own, so a chain of them used to recurse
|
||||||
|
const std::vector<uint8_t> input(500000, 0xC2);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input, true, true, json::cbor_tag_handler_t::ignore), "[json.exception.parse_error.110] parse error at byte 500001: syntax error while parsing CBOR value: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false, json::cbor_tag_handler_t::ignore).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a well-formed deep value is read through the SAX interface")
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> input(200000, 0x9F);
|
||||||
|
input.insert(input.end(), 200000, 0xFF);
|
||||||
|
|
||||||
|
SaxCountdown accept_all(1000000);
|
||||||
|
CHECK(json::sax_parse(input, &accept_all, json::input_format_t::cbor));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a well-formed deep value is read into a value")
|
||||||
|
{
|
||||||
|
const std::size_t depth = 10000;
|
||||||
|
std::vector<uint8_t> input(depth, 0x81);
|
||||||
|
input.push_back(0x00);
|
||||||
|
|
||||||
|
json j = json::from_cbor(input);
|
||||||
|
|
||||||
|
std::size_t measured = 0;
|
||||||
|
const json* p = &j;
|
||||||
|
while (p->is_array() && !p->empty())
|
||||||
|
{
|
||||||
|
p = &p->front();
|
||||||
|
++measured;
|
||||||
|
}
|
||||||
|
CHECK(measured == depth);
|
||||||
|
CHECK(p->is_number());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("containers are still read the same way")
|
||||||
|
{
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x80})) == json::array());
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA0})) == json::object());
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0xFF})) == json::array());
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0xFF})) == json::object());
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01, 0x02, 0xFF})) == json({1, 2}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 'a', 0x01, 0xFF})) == json({{"a", 1}}));
|
||||||
|
// definite and indefinite forms nested inside each other
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x82, 0x01, 0x02, 0xA1, 0x61, 'k', 0xBF, 0xFF, 0xFF})) == json({{1, 2}, {{"k", json::object()}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("tagged values are still read the same way")
|
||||||
|
{
|
||||||
|
const auto ignore = json::cbor_tag_handler_t::ignore;
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0x01}), true, true, ignore) == json(1));
|
||||||
|
// a chain of tags resolves to the value that follows it
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0xC2, 0xC2, 0x01}), true, true, ignore) == json(1));
|
||||||
|
// a tag inside a container, and one in front of a container
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x82, 0xC2, 0x01, 0x02}), true, true, ignore) == json({1, 2}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xC2, 0x82, 0x01, 0x02}), true, true, ignore) == json({1, 2}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("CBOR indefinite-length strings do not recurse per chunk")
|
||||||
|
{
|
||||||
|
// Reading an indefinite-length string or byte array used to call itself
|
||||||
|
// once per chunk, so a payload of repeated 0x7F (or 0x5F) bytes exhausted
|
||||||
|
// the call stack before any of the input was rejected. The open levels are
|
||||||
|
// counted now, and the levels below prove the reader still reads the same
|
||||||
|
// values and reports the same errors at the same byte offsets.
|
||||||
|
json _;
|
||||||
|
|
||||||
|
SECTION("many open levels are reported, not crashed on")
|
||||||
|
{
|
||||||
|
const std::vector<uint8_t> input(200000, 0x7F);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("many open levels are reported, not crashed on (binary)")
|
||||||
|
{
|
||||||
|
const std::vector<uint8_t> input(200000, 0x5F);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.parse_error.110] parse error at byte 200001: syntax error while parsing CBOR binary: unexpected end of input", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(input, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("chunks are still concatenated")
|
||||||
|
{
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0xFF})) == json(""));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x61, 0x61, 0xFF})) == json("a"));
|
||||||
|
// nested indefinite-length strings are concatenated across levels
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x61, 0x61, 0xFF, 0x61, 0x62, 0xFF})) == json("ab"));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x7F, 0x61, 0x7A, 0xFF, 0xFF, 0xFF})) == json("z"));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xA1, 0x7F, 0x61, 0x61, 0xFF, 0x01})) == json({{"a", 1}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("chunks are still concatenated (binary)")
|
||||||
|
{
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x41, 0x61, 0xFF})) == json::binary({0x61}));
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x41, 0x61, 0xFF, 0x41, 0x62, 0xFF})) == json::binary({0x61, 0x62}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a chunk that is not a string is still rejected")
|
||||||
|
{
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F, 0x7F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x00", json::parse_error&);
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x5F, 0x5F, 0x00})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR binary: expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x00", json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a break marker outside an indefinite-length string is not a string")
|
||||||
|
{
|
||||||
|
// 0xFF only closes a string that was opened; on its own it is not one
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0xA1, 0xFF, 0x01})), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing CBOR string: expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0xFF", json::parse_error&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("CBOR roundtrips" * doctest::skip())
|
TEST_CASE("CBOR roundtrips" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("input from flynn")
|
SECTION("input from flynn")
|
||||||
@@ -2300,7 +2484,7 @@ TEST_CASE("all CBOR first bytes")
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_CASE("examples from RFC 7049 Appendix A")
|
TEST_CASE("examples from RFC 8949 Appendix A")
|
||||||
{
|
{
|
||||||
SECTION("numbers")
|
SECTION("numbers")
|
||||||
{
|
{
|
||||||
@@ -2520,11 +2704,16 @@ TEST_CASE("Tagged values")
|
|||||||
const json j = "s";
|
const json j = "s";
|
||||||
auto v = json::to_cbor(j);
|
auto v = json::to_cbor(j);
|
||||||
|
|
||||||
SECTION("0xC6..0xD4")
|
const json j_bin_payload = json::binary(std::vector<std::uint8_t> {0x01, 0x02, 0x03});
|
||||||
|
auto v_bin_payload = json::to_cbor(j_bin_payload);
|
||||||
|
|
||||||
|
SECTION("0xC0..0xD7")
|
||||||
{
|
{
|
||||||
for (const auto b : std::vector<std::uint8_t>
|
for (const auto b : std::vector<std::uint8_t>
|
||||||
{
|
{
|
||||||
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4
|
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5,
|
||||||
|
0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4,
|
||||||
|
0xD5, 0xD6, 0xD7
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
CAPTURE(b);
|
CAPTURE(b);
|
||||||
@@ -2544,6 +2733,12 @@ TEST_CASE("Tagged values")
|
|||||||
|
|
||||||
auto j_tagged_stored = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::store);
|
auto j_tagged_stored = json::from_cbor(v_tagged, true, true, json::cbor_tag_handler_t::store);
|
||||||
CHECK(j_tagged_stored == j);
|
CHECK(j_tagged_stored == j);
|
||||||
|
|
||||||
|
auto v_binary_tagged = v_bin_payload;
|
||||||
|
v_binary_tagged.insert(v_binary_tagged.begin(), b);
|
||||||
|
auto j_binary_tagged_stored = json::from_cbor(v_binary_tagged, true, true, json::cbor_tag_handler_t::store);
|
||||||
|
CHECK(j_binary_tagged_stored == j_bin_payload);
|
||||||
|
CHECK(!j_binary_tagged_stored.get_binary().has_subtype());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <cstdlib> // strtod
|
||||||
|
#include <sstream> // stringstream
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// shortcut to scan a string literal
|
// shortcut to scan a string literal
|
||||||
@@ -224,3 +229,431 @@ TEST_CASE("lexer class")
|
|||||||
CHECK((scan_string("/**//**//**/", true) == json::lexer::token_type::end_of_input));
|
CHECK((scan_string("/**//**//**/", true) == json::lexer::token_type::end_of_input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer number fast path")
|
||||||
|
{
|
||||||
|
// The contiguous fast path (used for pointer/string input) must agree with
|
||||||
|
// the streaming byte path (used for std::istream) on token type, numeric
|
||||||
|
// value, and round-trip text for every well-formed number, and reject the
|
||||||
|
// same malformed numbers with the same message.
|
||||||
|
SECTION("contiguous vs streaming parity")
|
||||||
|
{
|
||||||
|
const std::vector<std::string> numbers =
|
||||||
|
{
|
||||||
|
"0", "-0", "1", "-1", "42", "-42", "10", "100", "1234567890",
|
||||||
|
"0.0", "-0.0", "3.14", "-3.14", "0.5", "-0.001", "123.456789",
|
||||||
|
"1e0", "1E0", "1e10", "1e-10", "1e+10", "1.5e3", "-2.5E-4",
|
||||||
|
"9223372036854775807", // INT64_MAX -> unsigned
|
||||||
|
"9223372036854775808", // INT64_MAX + 1 -> unsigned
|
||||||
|
"18446744073709551615", // UINT64_MAX -> unsigned
|
||||||
|
"18446744073709551616", // UINT64_MAX + 1 -> float
|
||||||
|
"-9223372036854775808", // INT64_MIN -> integer
|
||||||
|
"-9223372036854775809", // INT64_MIN - 1 -> float
|
||||||
|
"123456789012345678901234567890", // huge -> float
|
||||||
|
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
||||||
|
// high-precision / wide-exponent values that exercise the
|
||||||
|
// std::from_chars (Eisel-Lemire) path beyond the Clinger subset
|
||||||
|
"1.7976931348623157e308", "1.2345678901234567e-250",
|
||||||
|
"9007199254740993", "5e-324", "1e-320"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
|
||||||
|
// contiguous fast path
|
||||||
|
const json a = json::parse(doc);
|
||||||
|
// streaming byte path
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json b = json::parse(ss);
|
||||||
|
|
||||||
|
CAPTURE(n);
|
||||||
|
CHECK(a == b);
|
||||||
|
CHECK(a.dump() == b.dump());
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("significant-digit gate for the Clinger fast path")
|
||||||
|
{
|
||||||
|
// Clinger's fast path needs a significand below 2^53, so it cannot
|
||||||
|
// succeed once the mantissa has 17 or more significant digits (the
|
||||||
|
// significand would be at least 10^16). The lexer skips the attempt
|
||||||
|
// there. That is only allowed to save work: every value must still come
|
||||||
|
// out bit-exactly, and both scanners must agree. In particular the gate
|
||||||
|
// must not fire for tokens whose leading zeros merely look like extra
|
||||||
|
// digits - "0.1234567890123456" has 16 significant digits, not 17.
|
||||||
|
const std::vector<std::string> numbers =
|
||||||
|
{
|
||||||
|
"1234567890123456", // 16 significant digits
|
||||||
|
"12345678901234567", // 17 -> attempt skipped
|
||||||
|
"123456789012345678", // 18 -> attempt skipped
|
||||||
|
"0.1234567890123456", // 16: the leading "0" is not significant
|
||||||
|
"0.12345678901234567", // 17
|
||||||
|
"0.00000000000000001", // 1, in a long token
|
||||||
|
"0.000000000000000012345678901234", // 14, in a long token
|
||||||
|
"-0.0000000000000000000001", // 1, negative
|
||||||
|
"1.0000000000000000", // 17: trailing zeros are significant here
|
||||||
|
"10000000000000000", // 17
|
||||||
|
"9007199254740992", // 2^53
|
||||||
|
"9007199254740993", // 2^53 + 1
|
||||||
|
"-65.613616999999977", // canada.json shape
|
||||||
|
"1.2345678901234567e-250", // 17 with an exponent
|
||||||
|
"1.234567890123456e-250", // 16 with an exponent
|
||||||
|
"1e10", "0.0", "-0.0", "0e0", "0.000123"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
CAPTURE(n);
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
|
||||||
|
const json a = json::parse(doc); // contiguous fast path
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json b = json::parse(ss); // streaming byte path
|
||||||
|
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
CHECK(a == b);
|
||||||
|
|
||||||
|
if (a[0].is_number_float())
|
||||||
|
{
|
||||||
|
const double expected = std::strtod(n.c_str(), nullptr);
|
||||||
|
CHECK(a[0].get<double>() == expected);
|
||||||
|
CHECK(b[0].get<double>() == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("token type classification")
|
||||||
|
{
|
||||||
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
|
CHECK((scan_string("-1") == json::lexer::token_type::value_integer));
|
||||||
|
CHECK((scan_string("1.5") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("1e5") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("18446744073709551615") == json::lexer::token_type::value_unsigned));
|
||||||
|
CHECK((scan_string("18446744073709551616") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("-9223372036854775808") == json::lexer::token_type::value_integer));
|
||||||
|
CHECK((scan_string("-9223372036854775809") == json::lexer::token_type::value_float));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("malformed numbers are rejected identically")
|
||||||
|
{
|
||||||
|
for (const char* bad :
|
||||||
|
{"-", "1.", "1e", "1e+", "1.2e", "01", "-01", "1..2", "1.2.3"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(bad);
|
||||||
|
// the contiguous fast path must decline and let the byte path report
|
||||||
|
const std::string doc = std::string("[") + bad + "]";
|
||||||
|
CHECK_FALSE(json::accept(doc));
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
CHECK_FALSE(json::accept(ss));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// these sections parse invalid input, which aborts when exceptions are off
|
||||||
|
SECTION("exhaustive grammar parity with the streaming path")
|
||||||
|
{
|
||||||
|
// The JSON number grammar is encoded twice: once as the scan_number()
|
||||||
|
// state machine and once as the contiguous fast path. Enumerate every
|
||||||
|
// short string over the number alphabet and require the two encodings to
|
||||||
|
// agree exactly - on acceptance, on the reported error, and on the parsed
|
||||||
|
// value - so they cannot drift apart.
|
||||||
|
const std::string alphabet = "01.eE+-";
|
||||||
|
|
||||||
|
// full outcome of parsing @a doc, so a mismatch in type, value, or error
|
||||||
|
// message is caught, not just a mismatch in acceptance
|
||||||
|
const auto outcome = [](const std::string & doc, bool streaming) -> std::string
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (streaming)
|
||||||
|
{
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json j = json::parse(ss);
|
||||||
|
return std::string(j[0].type_name()) + '|' + j.dump();
|
||||||
|
}
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
return std::string(j[0].type_name()) + '|' + j.dump();
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
std::vector<std::string> tokens{""};
|
||||||
|
for (std::size_t length = 1; length <= 4; ++length)
|
||||||
|
{
|
||||||
|
std::vector<std::string> next;
|
||||||
|
next.reserve(tokens.size() * alphabet.size());
|
||||||
|
for (const auto& prefix : tokens)
|
||||||
|
{
|
||||||
|
for (const char c : alphabet)
|
||||||
|
{
|
||||||
|
next.push_back(prefix + c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens = next;
|
||||||
|
|
||||||
|
for (const auto& token : tokens)
|
||||||
|
{
|
||||||
|
const std::string doc = "[" + token + "]";
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7 + 49 + 343 + 2401 tokens
|
||||||
|
CHECK(tokens.size() == 2401);
|
||||||
|
CAPTURE(mismatches);
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("error positions match the streaming path")
|
||||||
|
{
|
||||||
|
// Rejecting identically is not enough: the fast path must also report the
|
||||||
|
// error at the same position as the byte path. A number directly followed
|
||||||
|
// by a newline is the interesting case, because the byte path reaches the
|
||||||
|
// newline (which resets the column) and then ungets it.
|
||||||
|
// returns the parse_error message, or "" if the document parsed
|
||||||
|
const auto contiguous_error = [](const std::string & doc) -> std::string
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
static_cast<void>(j);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
const auto streaming_error = [](const std::string & doc) -> std::string
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json j = json::parse(ss);
|
||||||
|
static_cast<void>(j);
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* bad :
|
||||||
|
{"[01\n]", "[00\n]", "[-01\n]", "{1\n}", "[1\n2]", "[1.2.3\n]",
|
||||||
|
"[1 \n2]", "[\n1\n2]", "1\n2", "[01\r\n]", "[1e\n]", "[-\n]"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(bad);
|
||||||
|
const std::string doc = bad;
|
||||||
|
const std::string contiguous_what = contiguous_error(doc);
|
||||||
|
|
||||||
|
CHECK_FALSE(contiguous_what.empty());
|
||||||
|
CHECK(contiguous_what == streaming_error(doc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A number terminated by a newline must report the same position as the
|
||||||
|
// same number terminated by anything else: scan_number() reads the
|
||||||
|
// terminator and ungets it, so the reported column is the one reached
|
||||||
|
// after the number's last character - not the 0 that an unget() across
|
||||||
|
// the newline used to leave behind.
|
||||||
|
CHECK(contiguous_error("[01\n]") == contiguous_error("[01 ]"));
|
||||||
|
CHECK(contiguous_error("[01\n]") ==
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 3: "
|
||||||
|
"syntax error while parsing array - unexpected number literal; expected ']'");
|
||||||
|
|
||||||
|
// the same for a multi-character token, where the column of the last
|
||||||
|
// character (the '3' of "-2.5e3") differs from the column it starts at
|
||||||
|
CHECK(contiguous_error("null -2.5e3\nfalse") == contiguous_error("null -2.5e3 false"));
|
||||||
|
CHECK(contiguous_error("null -2.5e3\nfalse") ==
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 11: "
|
||||||
|
"syntax error while parsing value - unexpected number literal; expected end of input");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer string fast path")
|
||||||
|
{
|
||||||
|
// Build a byte string from explicit values: a hex escape in a string
|
||||||
|
// literal swallows every following hex digit, which makes sequences like
|
||||||
|
// "\xC3\xA9b" mean something other than they look like.
|
||||||
|
const auto bytes = [](std::initializer_list<int> values)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
for (const int value : values)
|
||||||
|
{
|
||||||
|
result.push_back(static_cast<char>(value));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
// the full outcome of parsing @a doc: the parsed value, or the exact error
|
||||||
|
// message, so a mismatch in either is caught. Only usable with exceptions
|
||||||
|
// on: parsing invalid input aborts when they are off.
|
||||||
|
const auto outcome = [](const std::string & doc, bool streaming) -> std::string
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (streaming)
|
||||||
|
{
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json j = json::parse(ss);
|
||||||
|
return j.dump();
|
||||||
|
}
|
||||||
|
const json j = json::parse(doc);
|
||||||
|
return j.dump();
|
||||||
|
}
|
||||||
|
// not just parse_error: if a bulk scanner ever let ill-formed UTF-8
|
||||||
|
// through, dump() would throw type_error.316, and that has to surface
|
||||||
|
// as a reported mismatch rather than as an uncaught exception
|
||||||
|
catch (const json::exception& e)
|
||||||
|
{
|
||||||
|
return {e.what()};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// once at the start of the string, once past the first 8-byte SWAR word, so
|
||||||
|
// the bulk scanner sees each case with and without a run behind it
|
||||||
|
const std::vector<std::size_t> offsets{0, 9};
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("exhaustive contiguous vs streaming parity")
|
||||||
|
{
|
||||||
|
// ordinary ASCII, both specials, a control byte, characters that make
|
||||||
|
// the preceding backslash a valid escape, a UTF-8 lead byte of each
|
||||||
|
// length, a continuation byte, and a byte that is never valid
|
||||||
|
const std::vector<std::string> alphabet =
|
||||||
|
{
|
||||||
|
"a", "\"", "\\", "n", "u", "0", bytes({0x01}),
|
||||||
|
bytes({0xC3}), bytes({0xA9}), bytes({0xE4}), bytes({0xF0}),
|
||||||
|
bytes({0x80}), bytes({0xFF})
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
std::vector<std::string> tokens{""};
|
||||||
|
for (std::size_t length = 1; length <= 3; ++length)
|
||||||
|
{
|
||||||
|
std::vector<std::string> next;
|
||||||
|
next.reserve(tokens.size() * alphabet.size());
|
||||||
|
for (const auto& prefix : tokens)
|
||||||
|
{
|
||||||
|
for (const auto& symbol : alphabet)
|
||||||
|
{
|
||||||
|
next.push_back(prefix + symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokens = next;
|
||||||
|
|
||||||
|
for (const auto& token : tokens)
|
||||||
|
{
|
||||||
|
for (const std::size_t offset : offsets)
|
||||||
|
{
|
||||||
|
const std::string doc = "[\"" + std::string(offset, 'a') + token + "\"]";
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13 + 169 + 2197 tokens, each at two offsets
|
||||||
|
CHECK(tokens.size() == 2197);
|
||||||
|
CAPTURE(mismatches);
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("special bytes at every offset of the SWAR stride")
|
||||||
|
{
|
||||||
|
// The bulk scanner consumes 8 bytes at a time and then a tail; place
|
||||||
|
// every kind of byte that ends a run at each offset across two words,
|
||||||
|
// so multibyte sequences also straddle the word boundary.
|
||||||
|
const std::vector<std::string> specials =
|
||||||
|
{
|
||||||
|
"\"", "\\", 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})
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> mismatches;
|
||||||
|
for (std::size_t offset = 0; offset <= 17; ++offset)
|
||||||
|
{
|
||||||
|
for (const auto& special : specials)
|
||||||
|
{
|
||||||
|
const std::string doc = "[\"" + std::string(offset, 'a') + special + "\"]";
|
||||||
|
if (outcome(doc, false) != outcome(doc, true))
|
||||||
|
{
|
||||||
|
mismatches.push_back(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CAPTURE(mismatches);
|
||||||
|
CHECK(mismatches.empty());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// json::accept() never throws, so the ranges stay covered without exceptions
|
||||||
|
SECTION("UTF-8 ranges are accepted and rejected as documented")
|
||||||
|
{
|
||||||
|
// The bulk validator must accept exactly what the byte-at-a-time
|
||||||
|
// scanner accepts, so pin the boundaries of every range it recognizes.
|
||||||
|
// aggregate, only ever brace-initialized below; default member
|
||||||
|
// initializers would stop it being an aggregate in C++11
|
||||||
|
struct utf8_case // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
|
||||||
|
{
|
||||||
|
std::string sequence;
|
||||||
|
bool valid;
|
||||||
|
const char* description;
|
||||||
|
};
|
||||||
|
const std::vector<utf8_case> cases =
|
||||||
|
{
|
||||||
|
{bytes({0xC2, 0x80}), true, "U+0080, shortest two-byte"},
|
||||||
|
{bytes({0xDF, 0xBF}), true, "U+07FF, longest two-byte"},
|
||||||
|
{bytes({0xC1, 0xBF}), false, "overlong two-byte"},
|
||||||
|
{bytes({0xC2, 0x7F}), false, "two-byte with bad continuation"},
|
||||||
|
{bytes({0xE0, 0xA0, 0x80}), true, "U+0800, shortest three-byte"},
|
||||||
|
{bytes({0xE0, 0x9F, 0xBF}), false, "overlong three-byte"},
|
||||||
|
{bytes({0xED, 0x9F, 0xBF}), true, "U+D7FF, just below the surrogates"},
|
||||||
|
{bytes({0xED, 0xA0, 0x80}), false, "surrogate U+D800"},
|
||||||
|
{bytes({0xED, 0xBF, 0xBF}), false, "surrogate U+DFFF"},
|
||||||
|
{bytes({0xEE, 0x80, 0x80}), true, "U+E000, just above the surrogates"},
|
||||||
|
{bytes({0xEF, 0xBF, 0xBF}), true, "U+FFFF"},
|
||||||
|
{bytes({0xF0, 0x90, 0x80, 0x80}), true, "U+10000, shortest four-byte"},
|
||||||
|
{bytes({0xF0, 0x8F, 0xBF, 0xBF}), false, "overlong four-byte"},
|
||||||
|
{bytes({0xF4, 0x8F, 0xBF, 0xBF}), true, "U+10FFFF, highest code point"},
|
||||||
|
{bytes({0xF4, 0x90, 0x80, 0x80}), false, "above U+10FFFF"},
|
||||||
|
{bytes({0xF5, 0x80, 0x80, 0x80}), false, "lead byte out of range"},
|
||||||
|
{bytes({0x80}), false, "bare continuation byte"},
|
||||||
|
{bytes({0xFF}), false, "byte that never appears in UTF-8"},
|
||||||
|
{bytes({0xC3}), false, "truncated two-byte"},
|
||||||
|
{bytes({0xE4, 0xB8}), false, "truncated three-byte"},
|
||||||
|
{bytes({0xF0, 0x9F, 0x98}), false, "truncated four-byte"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test_case : cases)
|
||||||
|
{
|
||||||
|
CAPTURE(test_case.description);
|
||||||
|
for (const std::size_t offset : offsets)
|
||||||
|
{
|
||||||
|
CAPTURE(offset);
|
||||||
|
const std::string doc = "[\"" + std::string(offset, 'a') + test_case.sequence + "\"]";
|
||||||
|
CHECK(json::accept(doc) == test_case.valid);
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
CHECK(outcome(doc, false) == outcome(doc, true));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ using nlohmann::json;
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
class SaxEventLogger
|
class SaxEventLogger
|
||||||
@@ -344,6 +346,50 @@ void trailing_comma_helper(const std::string& s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
/**
|
||||||
|
* Validates that the generated JSON object is the same as expected
|
||||||
|
* Validates that the start position and end position match the start and end of the string
|
||||||
|
*
|
||||||
|
* This check assumes that there is no whitespace around the json object in the original string.
|
||||||
|
*/
|
||||||
|
void validate_generated_json_and_start_end_pos_helper(const std::string& original_string, const json& j, const json& check)
|
||||||
|
{
|
||||||
|
CHECK(j == check);
|
||||||
|
CHECK(j.start_pos() == 0);
|
||||||
|
CHECK(j.end_pos() == original_string.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the root object from the given root string and validates that the start and end positions for the nested object are correct.
|
||||||
|
*
|
||||||
|
* This checks that whitespace around the nested object is included in the start and end positions of the root object.
|
||||||
|
*/
|
||||||
|
void validate_start_end_pos_for_nested_obj_helper(const std::string& nested_type_json_str, const std::string& root_type_json_str, const json& expected_json, const json::parser_callback_t& cb = nullptr)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
|
||||||
|
// 1. If callback is provided, use callback version of parse()
|
||||||
|
if (cb)
|
||||||
|
{
|
||||||
|
j = json::parse(root_type_json_str, cb);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
j = json::parse(root_type_json_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check if the generated JSON is as expected
|
||||||
|
// Assumptions: The root_type_json_str does not have any whitespace around the json object
|
||||||
|
validate_generated_json_and_start_end_pos_helper(root_type_json_str, j, expected_json);
|
||||||
|
|
||||||
|
// 3. Get the nested object
|
||||||
|
const auto& nested = j["nested"];
|
||||||
|
// 4. Check if the start and end positions are generated correctly for nested objects and arrays
|
||||||
|
CHECK(nested_type_json_str == root_type_json_str.substr(nested.start_pos(), nested.end_pos() - nested.start_pos()));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
TEST_CASE("parser class")
|
TEST_CASE("parser class")
|
||||||
@@ -624,7 +670,8 @@ TEST_CASE("parser class")
|
|||||||
SECTION("overflow")
|
SECTION("overflow")
|
||||||
{
|
{
|
||||||
// overflows during parsing yield an exception
|
// overflows during parsing yield an exception
|
||||||
CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
// empty() is nodiscard; the exception is thrown by parser_helper() itself, before empty() would run
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(parser_helper("1.18973e+4932").empty()), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("invalid numbers")
|
SECTION("invalid numbers")
|
||||||
@@ -930,6 +977,98 @@ TEST_CASE("parser class")
|
|||||||
CHECK(accept_helper("+1") == false);
|
CHECK(accept_helper("+1") == false);
|
||||||
CHECK(accept_helper("+0") == false);
|
CHECK(accept_helper("+0") == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("issue #5411 - skip conversion when accept() does not need the numeric value")
|
||||||
|
{
|
||||||
|
// lexer::scan_number() may skip strtoull()/strtoll() for
|
||||||
|
// value_unsigned/value_integer tokens when the caller (e.g.
|
||||||
|
// json::accept()) does not need the converted value, as long
|
||||||
|
// as the digit count alone guarantees no 64-bit overflow (see
|
||||||
|
// the "safe_digit_count" fast path in scan_number()). This
|
||||||
|
// differential test checks that json::accept() (which enables
|
||||||
|
// the fast path) and json::parse() (which never does) always
|
||||||
|
// agree, over a corpus that exercises both the fast path
|
||||||
|
// (<=18 digits) and the untouched, exact fallback path (>=19
|
||||||
|
// digits) -- including reclassification of huge digit-only
|
||||||
|
// integers to a (possibly non-finite) floating-point value.
|
||||||
|
const std::vector<std::pair<std::string, bool>> cases =
|
||||||
|
{
|
||||||
|
// normal small/large integers, both signs
|
||||||
|
{"0", true}, {"1", true}, {"-1", true}, {"42", true}, {"-42", true},
|
||||||
|
{"123456789", true}, {"-123456789", true},
|
||||||
|
|
||||||
|
// digit-count boundary around the 18-digit safe cutoff (both signs)
|
||||||
|
{std::string(17, '9'), true},
|
||||||
|
{std::string(18, '9'), true},
|
||||||
|
{std::string(19, '9'), true},
|
||||||
|
{std::string(20, '9'), true},
|
||||||
|
{"-" + std::string(17, '9'), true},
|
||||||
|
{"-" + std::string(18, '9'), true},
|
||||||
|
{"-" + std::string(19, '9'), true},
|
||||||
|
{"-" + std::string(20, '9'), true},
|
||||||
|
|
||||||
|
// 64-bit boundaries
|
||||||
|
{"9223372036854775807", true}, // INT64_MAX
|
||||||
|
{"-9223372036854775808", true}, // INT64_MIN
|
||||||
|
{"18446744073709551615", true}, // UINT64_MAX
|
||||||
|
{"18446744073709551616", true}, // UINT64_MAX + 1 (overflows uint64_t, finite double)
|
||||||
|
|
||||||
|
// the 28-digit example from the issue: overflows uint64_t
|
||||||
|
// but is finite as a double, so the scanner reclassifies
|
||||||
|
// it to value_float and it is accepted
|
||||||
|
{"9999999999999999999999999999", true},
|
||||||
|
|
||||||
|
// huge digit-only integers that overflow even a double -> rejected
|
||||||
|
{std::string(309, '9'), false},
|
||||||
|
{std::string(400, '9'), false},
|
||||||
|
{"1" + std::string(400, '0'), false},
|
||||||
|
|
||||||
|
// 1e999 / 1e400 style overflow -> rejected
|
||||||
|
{"1e999", false},
|
||||||
|
{"1e400", false},
|
||||||
|
{"-1e999", false},
|
||||||
|
{"1E999", false},
|
||||||
|
|
||||||
|
// values straddling DBL_MAX
|
||||||
|
{"1.7976931348623157e308", true}, // <= DBL_MAX, finite
|
||||||
|
{"1.7976931348623159e308", false}, // > DBL_MAX, overflows to inf
|
||||||
|
|
||||||
|
// a mix of other valid/invalid numeric syntax
|
||||||
|
{"3.14159", true},
|
||||||
|
{"-0.0", true},
|
||||||
|
{"1.0e10", true},
|
||||||
|
{"01", false},
|
||||||
|
{"-", false},
|
||||||
|
{"1.", false},
|
||||||
|
{"1e", false},
|
||||||
|
{"+1", false},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& c : cases)
|
||||||
|
{
|
||||||
|
const std::string& number = c.first;
|
||||||
|
const bool expected = c.second;
|
||||||
|
CAPTURE(number)
|
||||||
|
CAPTURE(expected)
|
||||||
|
|
||||||
|
// accept() takes the fast path (skips conversion when possible)
|
||||||
|
CHECK(json::accept(number) == expected);
|
||||||
|
|
||||||
|
// parse() always performs the full conversion; it must agree
|
||||||
|
json j;
|
||||||
|
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(number), nullptr, false).parse(true, j));
|
||||||
|
CHECK(!j.is_discarded() == expected);
|
||||||
|
|
||||||
|
// wrap in an array so get_token() is exercised beyond the
|
||||||
|
// very first (constructor-time) scan as well
|
||||||
|
std::string wrapped = "[";
|
||||||
|
wrapped += number;
|
||||||
|
wrapped += ",";
|
||||||
|
wrapped += number;
|
||||||
|
wrapped += "]";
|
||||||
|
CHECK(json::accept(wrapped) == expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1394,6 +1533,71 @@ TEST_CASE("parser class")
|
|||||||
CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
|
CHECK(accept_helper("\"\\uD80C\\uFFFF\"") == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(JSON_NOEXCEPTION)
|
||||||
|
SECTION("issue #5412 - whitespace skipping bookkeeping (compact vs. pretty-printed)")
|
||||||
|
{
|
||||||
|
// lexer::skip_whitespace() reads its first character with get() (to
|
||||||
|
// honor a possibly pending unget() from the previous token) and every
|
||||||
|
// further whitespace character with get_ignoring_pending_unget() (a
|
||||||
|
// get() variant that skips the then-always-false next_unget check).
|
||||||
|
// This must not change the reported byte offset, line, or column of
|
||||||
|
// a syntax error, even when a long run of whitespace containing
|
||||||
|
// multiple newlines is skipped beforehand (as with pretty-printed
|
||||||
|
// input). The expected values below were captured from the
|
||||||
|
// unmodified do-while(get()) loop, so any regression that miscounts
|
||||||
|
// characters or newlines while skipping whitespace changes them.
|
||||||
|
const auto check_error = [](const std::string & input, std::size_t expected_byte,
|
||||||
|
const std::string & expected_what)
|
||||||
|
{
|
||||||
|
CAPTURE(input)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
json _ = json::parse(input);
|
||||||
|
FAIL_CHECK("expected a parse_error, but parsing succeeded");
|
||||||
|
}
|
||||||
|
catch (const json::parse_error& e)
|
||||||
|
{
|
||||||
|
CHECK(e.byte == expected_byte);
|
||||||
|
CHECK(std::string(e.what()) == expected_what);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// a nested document, serialized both compactly and pretty-printed
|
||||||
|
// (dump(4)), each truncated right before the final closing '}' so
|
||||||
|
// that the parser hits EOF after skipping all of the (in the
|
||||||
|
// pretty-printed case, substantial) indentation whitespace
|
||||||
|
const json doc =
|
||||||
|
{
|
||||||
|
{"a", 1},
|
||||||
|
{"b", json::array({true, false, nullptr, "x"})},
|
||||||
|
{"c", json::object({{"d", 3.14}, {"e", json::array({1, 2, 3})}})}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string compact = doc.dump();
|
||||||
|
const std::string pretty = doc.dump(4);
|
||||||
|
|
||||||
|
check_error(compact.substr(0, compact.size() - 1), 60,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 60: syntax error while parsing object - unexpected end of input; expected '}'");
|
||||||
|
check_error(pretty.substr(0, pretty.size() - 1), 193,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 17, column 1: syntax error while parsing object - unexpected end of input; expected '}'");
|
||||||
|
|
||||||
|
// an invalid token appearing after several indented, multi-line
|
||||||
|
// whitespace runs vs. the same document without any of that
|
||||||
|
// whitespace
|
||||||
|
check_error(R"({
|
||||||
|
"a": 1,
|
||||||
|
"b": [
|
||||||
|
true,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"c": @
|
||||||
|
})", 70,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 7, column 10: syntax error while parsing value - invalid literal; last read: '\"c\": @'");
|
||||||
|
check_error(R"({"a":1,"b":[true,false],"c":@})", 29,
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 29: syntax error while parsing value - invalid literal; last read: '\"c\":@'");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
SECTION("tests found by mutate++")
|
SECTION("tests found by mutate++")
|
||||||
{
|
{
|
||||||
// test case to make sure no comma precedes the first key
|
// test case to make sure no comma precedes the first key
|
||||||
@@ -1440,6 +1644,13 @@ TEST_CASE("parser class")
|
|||||||
]
|
]
|
||||||
)";
|
)";
|
||||||
|
|
||||||
|
const auto* structured_object = R"(
|
||||||
|
{
|
||||||
|
"foo": [1, 2],
|
||||||
|
"bar": 3
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
SECTION("filter nothing")
|
SECTION("filter nothing")
|
||||||
{
|
{
|
||||||
const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
|
||||||
@@ -1515,6 +1726,100 @@ TEST_CASE("parser class")
|
|||||||
CHECK (j_filtered2 == json({1}));
|
CHECK (j_filtered2 == json({1}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("filter array in object")
|
||||||
|
{
|
||||||
|
// the array is discarded once it is already stored under its key
|
||||||
|
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
|
||||||
|
{
|
||||||
|
return e != json::parse_event_t::array_end;
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK (j_filtered1 == json({{"bar", 3}}));
|
||||||
|
|
||||||
|
// the array is discarded before it is stored, leaving the
|
||||||
|
// placeholder the key event wrote
|
||||||
|
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
|
||||||
|
{
|
||||||
|
return e != json::parse_event_t::array_start;
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK (j_filtered2 == json({{"bar", 3}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("filter value in object")
|
||||||
|
{
|
||||||
|
// the value is discarded after its key was kept, leaving the
|
||||||
|
// placeholder the key event wrote
|
||||||
|
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
|
||||||
|
{
|
||||||
|
return !(e == json::parse_event_t::value && parsed == json(3));
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK (j_filtered1 == json({{"foo", {1, 2}}}));
|
||||||
|
|
||||||
|
// the same value is discarded together with its key, so no
|
||||||
|
// placeholder was stored for it
|
||||||
|
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
|
||||||
|
{
|
||||||
|
return !((e == json::parse_event_t::key && parsed == json("bar")) ||
|
||||||
|
(e == json::parse_event_t::value && parsed == json(3)));
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK (j_filtered2 == json({{"foo", {1, 2}}}));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("filter many members of one container")
|
||||||
|
{
|
||||||
|
// Rejecting a value makes the parser remove the placeholder its key
|
||||||
|
// event stored. Locating that placeholder used to be a scan of the
|
||||||
|
// whole parent, which made filtering a large container quadratic:
|
||||||
|
// 128k members took ~25 s. These cases keep many members alive
|
||||||
|
// while discarding many others, so the removal cost is the whole
|
||||||
|
// point; they run in milliseconds when the placeholder is erased
|
||||||
|
// directly.
|
||||||
|
constexpr int count = 20000;
|
||||||
|
|
||||||
|
std::string s = "{";
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
// "a<i>" is kept, "z<i>" is discarded
|
||||||
|
s += "\"a" + std::to_string(i) + "\":" + std::to_string(i) + ",";
|
||||||
|
s += "\"z" + std::to_string(i) + "\":-1,";
|
||||||
|
}
|
||||||
|
s.back() = '}';
|
||||||
|
|
||||||
|
const json j_values = json::parse(s, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
|
||||||
|
{
|
||||||
|
return !(e == json::parse_event_t::value && parsed == json(-1));
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK(j_values.size() == count);
|
||||||
|
CHECK(j_values.at("a0") == json(0));
|
||||||
|
CHECK(j_values.at("a" + std::to_string(count - 1)) == json(count - 1));
|
||||||
|
CHECK_FALSE(j_values.contains("z0"));
|
||||||
|
CHECK_FALSE(j_values.contains("z" + std::to_string(count - 1)));
|
||||||
|
|
||||||
|
// the same, but discarding whole containers rather than values,
|
||||||
|
// which takes the end_object()/end_array() removal path
|
||||||
|
std::string s_nested = "{";
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
s_nested += "\"a" + std::to_string(i) + "\":" + std::to_string(i) + ",";
|
||||||
|
s_nested += "\"z" + std::to_string(i) + "\":[1,2],";
|
||||||
|
}
|
||||||
|
s_nested.back() = '}';
|
||||||
|
|
||||||
|
const json j_arrays = json::parse(s_nested, [](int /*unused*/, json::parse_event_t e, const json& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return e != json::parse_event_t::array_end;
|
||||||
|
});
|
||||||
|
|
||||||
|
CHECK(j_arrays.size() == count);
|
||||||
|
CHECK(j_arrays.at("a0") == json(0));
|
||||||
|
CHECK_FALSE(j_arrays.contains("z0"));
|
||||||
|
CHECK_FALSE(j_arrays.contains("z" + std::to_string(count - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("filter specific events")
|
SECTION("filter specific events")
|
||||||
{
|
{
|
||||||
SECTION("first closing event")
|
SECTION("first closing event")
|
||||||
@@ -1730,6 +2035,228 @@ TEST_CASE("parser class")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::parse("/a", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid comment; expecting '/' or '*' after '/'; last read: '/a'", json::parse_error);
|
||||||
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
|
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
// Macro for all test cases for start_pos and end_pos
|
||||||
|
#define SETUP_TESTCASES() \
|
||||||
|
SECTION("with callback") \
|
||||||
|
{ \
|
||||||
|
SECTION("filter nothing") \
|
||||||
|
{ \
|
||||||
|
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept \
|
||||||
|
{ \
|
||||||
|
return true; \
|
||||||
|
}; \
|
||||||
|
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected, cb); \
|
||||||
|
} \
|
||||||
|
SECTION("filter element") \
|
||||||
|
{ \
|
||||||
|
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t event, json& j) noexcept \
|
||||||
|
{ \
|
||||||
|
return (event != json::parse_event_t::key && event != json::parse_event_t::value) || j != json("a"); \
|
||||||
|
}; \
|
||||||
|
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, filteredExpected, cb); \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
SECTION("without callback") \
|
||||||
|
{ \
|
||||||
|
validate_start_end_pos_for_nested_obj_helper(nested_type_json_str, root_type_json_str, expected); \
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("retrieve start position and end position")
|
||||||
|
{
|
||||||
|
SECTION("for object")
|
||||||
|
{
|
||||||
|
// Create an object with spaces to test the start and end positions. Spaces will not be included in the
|
||||||
|
// JSON object, however, the start and end positions should include the spaces from the input JSON string.
|
||||||
|
const std::string nested_type_json_str = R"({ "a": 1,"b" : "test1"})";
|
||||||
|
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test2"})";
|
||||||
|
auto expected = json({{"nested", {{"a", 1}, {"b", "test1"}}}, {"anotherValue", "test2"}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected["nested"].erase("a");
|
||||||
|
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("for array")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"(["a", "test", 45])";
|
||||||
|
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", {"a", "test", 45}}, {"anotherValue", "test"}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected["nested"] = json({"test", 45});
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("for array with objects")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"([{"a": 1, "b": "test"}, {"c": 2, "d": "test2"}])";
|
||||||
|
const std::string root_type_json_str = R"({ "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", {{{"a", 1}, {"b", "test"}}, {{"c", 2}, {"d", "test2"}}}}, {"anotherValue", "test"}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected["nested"][0].erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
|
||||||
|
auto j = json::parse(root_type_json_str);
|
||||||
|
auto nested_array = j["nested"];
|
||||||
|
const auto& nested_obj = nested_array[0];
|
||||||
|
CHECK(nested_type_json_str.substr(1, 21) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
|
||||||
|
CHECK(nested_type_json_str.substr(24, 22) == root_type_json_str.substr(nested_array[1].start_pos(), nested_array[1].end_pos() - nested_array[1].start_pos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("for two levels of nesting objects")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"({"nested2": {"b": "test"}})";
|
||||||
|
const std::string root_type_json_str = R"({ "a": 2, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"a", 2}, {"nested", {{"nested2", {{"b", "test"}}}}}, {"anotherValue", "test"}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected.erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
|
||||||
|
auto j = json::parse(root_type_json_str);
|
||||||
|
auto nested_obj = j["nested"]["nested2"];
|
||||||
|
CHECK(nested_type_json_str.substr(12, 13) == root_type_json_str.substr(nested_obj.start_pos(), nested_obj.end_pos() - nested_obj.start_pos()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("for simple types")
|
||||||
|
{
|
||||||
|
SECTION("no nested")
|
||||||
|
{
|
||||||
|
SECTION("with callback")
|
||||||
|
{
|
||||||
|
json::parser_callback_t const cb = [](int /*unused*/, json::parse_event_t /*unused*/, json& /*unused*/) noexcept
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. string type
|
||||||
|
std::string json_str = R"("test")";
|
||||||
|
auto j = json::parse(json_str, cb);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
|
||||||
|
|
||||||
|
// 2. number type
|
||||||
|
json_str = R"(1)";
|
||||||
|
j = json::parse(json_str, cb);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
|
||||||
|
|
||||||
|
// 3. boolean type
|
||||||
|
json_str = R"(true)";
|
||||||
|
j = json::parse(json_str, cb);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, true);
|
||||||
|
|
||||||
|
// 4. null type
|
||||||
|
json_str = R"(null)";
|
||||||
|
j = json::parse(json_str, cb);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("without callback")
|
||||||
|
{
|
||||||
|
// 1. string type
|
||||||
|
std::string json_str = R"("test")";
|
||||||
|
auto j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, "test");
|
||||||
|
|
||||||
|
// 2. number type
|
||||||
|
json_str = R"(1)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, 1);
|
||||||
|
|
||||||
|
json_str = R"(1.001239923)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, 1.001239923);
|
||||||
|
|
||||||
|
json_str = R"(1.123812389000000)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, 1.123812389);
|
||||||
|
|
||||||
|
// 3. boolean type
|
||||||
|
json_str = R"(true)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, true);
|
||||||
|
|
||||||
|
json_str = R"(false)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, false);
|
||||||
|
|
||||||
|
// 4. null type
|
||||||
|
json_str = R"(null)";
|
||||||
|
j = json::parse(json_str);
|
||||||
|
validate_generated_json_and_start_end_pos_helper(json_str, j, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("string type")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"("test")";
|
||||||
|
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", "test"}, {"anotherValue", "test"}, {"a", 1}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected.erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("number type")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"(2)";
|
||||||
|
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", 2}, {"anotherValue", "test"}, {"a", 1}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected.erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("boolean type")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"(true)";
|
||||||
|
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", true}, {"anotherValue", "test"}, {"a", 1}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected.erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("null type")
|
||||||
|
{
|
||||||
|
const std::string nested_type_json_str = R"(null)";
|
||||||
|
const std::string root_type_json_str = R"({ "a": 1, "nested": )" + nested_type_json_str + R"(, "anotherValue": "test" })";
|
||||||
|
auto expected = json({{"nested", nullptr}, {"anotherValue", "test"}, {"a", 1}});
|
||||||
|
auto filteredExpected = expected;
|
||||||
|
filteredExpected.erase("a");
|
||||||
|
SETUP_TESTCASES()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SECTION("with leading whitespace and newlines around root JSON")
|
||||||
|
{
|
||||||
|
const std::string initial_whitespace = R"(
|
||||||
|
|
||||||
|
)";
|
||||||
|
const std::string nested_type_json_str = R"({
|
||||||
|
"a": 1,
|
||||||
|
"nested": {
|
||||||
|
"b": "test"
|
||||||
|
},
|
||||||
|
"anotherValue": "test"
|
||||||
|
})";
|
||||||
|
const std::string end_whitespace = R"(
|
||||||
|
|
||||||
|
)";
|
||||||
|
const std::string root_type_json_str = initial_whitespace + nested_type_json_str + end_whitespace;
|
||||||
|
|
||||||
|
auto expected = json({{"a", 1}, {"nested", {{"b", "test"}}}, {"anotherValue", "test"}});
|
||||||
|
|
||||||
|
auto j = json::parse(root_type_json_str);
|
||||||
|
|
||||||
|
// 2. Check if the generated JSON is as expected
|
||||||
|
CHECK(j == expected);
|
||||||
|
|
||||||
|
// 3. Check if the start and end positions do not include the surrounding whitespace
|
||||||
|
CHECK(j.start_pos() == initial_whitespace.size());
|
||||||
|
CHECK(j.end_pos() == root_type_json_str.size() - end_whitespace.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef SETUP_TESTCASES
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// this test relies on parse errors being thrown, so it is skipped when
|
// this test relies on parse errors being thrown, so it is skipped when
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user