mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 18:57:13 +00:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46f269e3f | ||
|
|
b41228c227 | ||
|
|
dcb96e82fb | ||
|
|
c7fd5ac19f | ||
|
|
a678aa6dd7 | ||
|
|
3d93f682a7 | ||
|
|
1c696dcf05 | ||
|
|
7cd34c62a9 | ||
|
|
eb74fe2ab7 | ||
|
|
91b9ee2602 | ||
|
|
ce28c722bc | ||
|
|
6261d1e432 | ||
|
|
ed86019a21 | ||
|
|
4f11512641 | ||
|
|
6a27b9acb2 | ||
|
|
91869983a0 | ||
|
|
4530d9681c | ||
|
|
ca7740fef2 | ||
|
|
6ba53e63dd | ||
|
|
0a4b3a84ee | ||
|
|
0c4e7c103c | ||
|
|
a6e9785ab6 | ||
|
|
d1377a1e84 | ||
|
|
4e294f4b18 |
No files matched your search
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
mkdir -p ./pr
|
||||
echo ${{ github.event.number }} > ./pr/number
|
||||
echo ${{ github.event.pull_request.user.login }} > ./pr/author
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: pr
|
||||
path: pr/
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -170,7 +170,7 @@ jobs:
|
||||
# instead of installing the pinned astyle version locally.
|
||||
- name: Upload patch
|
||||
if: steps.diff.outputs.has_diff == 'true'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: amalgamation-patch
|
||||
path: patch/amalgamation.patch
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -47,6 +47,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -80,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
|
||||
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Check that Meson and pkg-config offer the CMake options
|
||||
run: make check_build_options
|
||||
- name: Install Meson
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -92,14 +92,14 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_infer
|
||||
- name: Archive Infer report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: infer-report
|
||||
path: ${{ github.workspace }}/build/build_infer/infer-out/report.txt
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
target: [ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -197,7 +197,7 @@ jobs:
|
||||
- name: Build
|
||||
run: cmake --build build --target ci_test_coverage
|
||||
- name: Archive coverage report
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: ${{ github.workspace }}/build/html
|
||||
@@ -244,7 +244,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -262,7 +262,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -279,7 +279,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Disable filesystem support (std::filesystem is unusable) for clang 7 / 8 / 9 / 10
|
||||
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' }}
|
||||
@@ -299,7 +299,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -319,7 +319,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build with libc++
|
||||
@@ -346,7 +346,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
# 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.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||
@@ -392,7 +392,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -409,7 +409,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -423,7 +423,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DJSON_CI=On
|
||||
- name: Build
|
||||
@@ -433,7 +433,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -443,7 +443,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||
- name: Build
|
||||
@@ -456,7 +456,7 @@ jobs:
|
||||
target: [ci_test_examples, ci_test_build_documentation, ci_test_documentation_mermaid]
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@351661ca32ac09a36dc5ee2d536e3128f2a3c8ed # v2.22.0
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -465,7 +465,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
# the git-revision-date-localized plugin needs the history; a shallow clone makes the strict build fail
|
||||
fetch-depth: ${{ matrix.target == 'ci_test_build_documentation' && '0' || '1' }}
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
if: matrix.target == 'ci_test_documentation_mermaid'
|
||||
with:
|
||||
node-version: 24
|
||||
@@ -484,7 +484,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Check that the job runs natively on arm64
|
||||
run: uname -m && test "$(uname -m)" = aarch64
|
||||
- name: Run CMake
|
||||
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Get latest CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
- name: Set extra CXX_FLAGS for latest std_version
|
||||
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
||||
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
||||
@@ -214,7 +214,7 @@ jobs:
|
||||
# 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.
|
||||
- name: Get pinned CMake and ninja
|
||||
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
|
||||
uses: lukka/get-cmake@a89e43e22a96abafcd7a62db652a65ecd33052c9 # v4.4.4
|
||||
with:
|
||||
cmakeVersion: 4.3.4
|
||||
# CMAKE_CXX_FLAGS replaces CMake's MSVC defaults (which include /EHsc), and
|
||||
|
||||
+8
-1
@@ -485,6 +485,13 @@ add_custom_target(ci_clang_analyze
|
||||
# Check code with Cppcheck.
|
||||
###############################################################################
|
||||
|
||||
# default_defines.hpp expands the Hedley macros to the compiler's feature-test operators, which Cppcheck does not know.
|
||||
# Since Cppcheck 2.20, it rejects their invocations in #if conditions instead of evaluating them as 0.
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp "")
|
||||
foreach(OPERATOR __has_attribute __has_builtin __has_cpp_attribute __has_declspec_attribute __has_extension __has_feature __has_include __has_warning)
|
||||
file(APPEND ${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp "#define ${OPERATOR}(x) 0\n")
|
||||
endforeach()
|
||||
|
||||
add_custom_target(ci_cppcheck
|
||||
COMMAND mkdir -p cppcheck
|
||||
COMMAND clang -dM -E -x c++ -std=c++11 ${CMAKE_SOURCE_DIR}/include/nlohmann/thirdparty/hedley/hedley.hpp > default_defines.hpp 2> /dev/null
|
||||
@@ -493,7 +500,7 @@ add_custom_target(ci_cppcheck
|
||||
COMMAND venv_cppcheck/bin/cppcheck --enable=warning --check-level=exhaustive --inline-suppr --inconclusive --force
|
||||
--std=c++11 ${PROJECT_SOURCE_DIR}/include/nlohmann/json.hpp -I ${CMAKE_SOURCE_DIR}/include
|
||||
--error-exitcode=1 --relative-paths=${PROJECT_SOURCE_DIR} -j ${N} --include=default_defines.hpp
|
||||
--cppcheck-build-dir=cppcheck
|
||||
--include=${CMAKE_BINARY_DIR}/cppcheck_feature_tests.hpp --cppcheck-build-dir=cppcheck
|
||||
-UJSON_CATCH_USER -UJSON_TRY_USER -UJSON_ASSERT -UJSON_INTERNAL_CATCH -UJSON_THROW
|
||||
-DJSON_HAS_CPP_11 -UJSON_HAS_CPP_14 -UJSON_HAS_CPP_17 -UJSON_HAS_CPP_20 -UJSON_HAS_THREE_WAY_COMPARISON
|
||||
COMMENT "Check code with Cppcheck"
|
||||
|
||||
@@ -1 +1 @@
|
||||
cppcheck==1.5.1
|
||||
cppcheck==1.5.2
|
||||
@@ -27,7 +27,7 @@ To store objects in C++, a type is defined by the template parameters explained
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default value for
|
||||
With the default values for `ArrayType` (`std::vector`) and `AllocatorType` (`std::allocator`), the default type for
|
||||
`array_t` is:
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -68,6 +68,14 @@ basic_json(basic_json&& other) noexcept;
|
||||
`std::unordered_map`, `std::multimap`, and `std::unordered_multimap` with a `key_type` compatible to `string_t`
|
||||
and a `value_type` from which a `basic_json` value can be constructed.
|
||||
- **strings**: `string_t`, string literals, and all compatible string containers can be used.
|
||||
|
||||
!!! note "Rvalue containers are moved from"
|
||||
|
||||
If `val` is an rvalue of a compatible array-like or object-like container other than `array_t`/`object_t`
|
||||
(e.g., `json j = std::move(vec);` or `json j(std::vector<std::string>{...});`), the elements are moved into
|
||||
the JSON value instead of copied. Afterwards, the elements of the source container are in a valid but
|
||||
unspecified state (**moved-from**). Keys of associative containers are `const` and therefore still copied.
|
||||
Lvalues and `const` rvalues are copied, and the source is left unchanged.
|
||||
- **numbers**: [`number_integer_t`](number_integer_t.md), [`number_unsigned_t`](number_unsigned_t.md),
|
||||
[`number_float_t`](number_float_t.md), and all convertible number types such as `int`, `size_t`, `int64_t`, `float`
|
||||
or `double` can be used.
|
||||
@@ -236,7 +244,9 @@ basic_json(basic_json&& other) noexcept;
|
||||
2. No-throw guarantee: this constructor never throws exceptions.
|
||||
3. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
|
||||
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
|
||||
JSON value.
|
||||
JSON value. **Exception:** when `val` is an rvalue array-like or object-like container, its elements are moved one
|
||||
by one; if an exception is thrown in the middle of the conversion (e.g., an allocation failure), the JSON value is
|
||||
unchanged, but the source container may be left partially moved-from.
|
||||
4. Depends on the called constructor. For types directly supported by the library (i.e., all types for which no
|
||||
`to_json()` function was provided), a strong guarantee holds: if an exception is thrown, there are no changes to any
|
||||
JSON value.
|
||||
|
||||
@@ -23,7 +23,7 @@ To store boolean values in C++, a type is defined by the template parameter `Bo
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `BooleanType` (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for `BooleanType` (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ constexpr bool is_number_unsigned() const noexcept;
|
||||
This function returns `#!cpp true` if and only if the JSON value is an unsigned integer number. This excludes
|
||||
floating-point and signed integer values.
|
||||
|
||||
If [`number_unsigned_t`](number_unsigned_t.md) is the same type as [`number_integer_t`](number_integer_t.md), every
|
||||
integer is stored as `number_integer_t`, and this function always returns `#!cpp false`.
|
||||
|
||||
## Return value
|
||||
|
||||
`#!cpp true` if type is an unsigned integer number, `#!cpp false` otherwise.
|
||||
|
||||
@@ -35,7 +35,7 @@ type to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberFloatType` (`double`), the default value for `number_float_t` is `#!cpp double`.
|
||||
With the default values for `NumberFloatType` (`double`), the default type for `number_float_t` is `#!cpp double`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -52,8 +52,9 @@ With the default values for `NumberFloatType` (`double`), the default value for
|
||||
> interoperability can be achieved by implementations that expect no more precision or range than these provide, in the
|
||||
> sense that implementations will approximate JSON numbers within the expected precision.
|
||||
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that
|
||||
numbers smaller than `-1.79769313486232e+308` or greater than `1.79769313486232e+308` are rejected during parsing with
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default.
|
||||
Note that with the default `double`, numbers smaller than `-1.79769313486232e+308` or greater than
|
||||
`1.79769313486232e+308` are rejected during parsing with
|
||||
[`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can
|
||||
only be stored by assigning them directly and are serialized to `null`.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ to use.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberIntegerType` (`std::int64_t`), the default value for `number_integer_t` is
|
||||
With the default values for `NumberIntegerType` (`std::int64_t`), the default type for `number_integer_t` is
|
||||
`#!cpp std::int64_t`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -25,14 +25,15 @@ the type to use.
|
||||
`NumberUnsignedType`
|
||||
: the type to store unsigned integers. It must be an **unsigned integral** type (`#!cpp std::is_integral`) with a
|
||||
`#!cpp std::numeric_limits` specialization, and it must be able to represent the absolute value of every
|
||||
[`number_integer_t`](number_integer_t.md) value. See
|
||||
[`number_integer_t`](number_integer_t.md) value. Alternatively, it can be the same type as
|
||||
[`number_integer_t`](number_integer_t.md); then every integer is stored as `number_integer_t`. See
|
||||
[Template Parameter Requirements](../../features/types/template_parameters.md#numberintegertype-and-numberunsignedtype).
|
||||
|
||||
## Notes
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default value for `number_unsigned_t` is
|
||||
With the default values for `NumberUnsignedType` (`std::uint64_t`), the default type for `number_unsigned_t` is
|
||||
`#!cpp std::uint64_t`.
|
||||
|
||||
### Default behavior
|
||||
@@ -89,3 +90,4 @@ Integer number values are stored directly inside a `basic_json` type.
|
||||
## Version history
|
||||
|
||||
- Added in version 2.0.0.
|
||||
- Can be the same type as `number_integer_t` since version 3.13.0.
|
||||
@@ -36,7 +36,7 @@ To store objects in C++, a type is defined by the template parameters described
|
||||
### Default type
|
||||
|
||||
With the default values for `ObjectType` (`std::map`), `StringType` (`std::string`), and `AllocatorType`
|
||||
(`std::allocator`), the default value for `object_t` is:
|
||||
(`std::allocator`), the default type for `object_t` is:
|
||||
|
||||
```cpp
|
||||
// until C++14
|
||||
|
||||
@@ -32,7 +32,7 @@ JSON class into byte-sized characters during deserialization.
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for `StringType` (`std::string`), the default value for `string_t` is `#!cpp std::string`.
|
||||
With the default values for `StringType` (`std::string`), the default type for `string_t` is `#!cpp std::string`.
|
||||
|
||||
### Encoding
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ requirements are collected in [Template Parameter Requirements](template_paramet
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default value for `object_t` is:
|
||||
With the default values for *ObjectType* (`std::map`), *StringType* (`std::string`), and *AllocatorType* (`std::allocator`), the default type for `object_t` is:
|
||||
|
||||
```cpp
|
||||
std::map<
|
||||
@@ -177,7 +177,7 @@ json j = m;
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default value for `array_t` is:
|
||||
With the default values for *ArrayType* (`std::vector`) and *AllocatorType* (`std::allocator`), the default type for `array_t` is:
|
||||
|
||||
```cpp
|
||||
std::vector<
|
||||
@@ -209,7 +209,7 @@ Unicode values are split by the JSON class into byte-sized characters during des
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *StringType* (`std::string`), the default value for `string_t` is `#!cpp std::string`.
|
||||
With the default values for *StringType* (`std::string`), the default type for `string_t` is `#!cpp std::string`.
|
||||
|
||||
### Encoding
|
||||
|
||||
@@ -234,7 +234,7 @@ String values are stored as pointers in a `basic_json` type. That is, for any ac
|
||||
|
||||
### Default type
|
||||
|
||||
With the default values for *BooleanType* (`#!cpp bool`), the default value for `boolean_t` is `#!cpp bool`.
|
||||
With the default values for *BooleanType* (`#!cpp bool`), the default type for `boolean_t` is `#!cpp bool`.
|
||||
|
||||
### Storage
|
||||
|
||||
@@ -252,9 +252,9 @@ This description includes both integer and floating-point numbers. However, C++
|
||||
|
||||
### Default types
|
||||
|
||||
With the default values for *NumberIntegerType* (`std::int64_t`), the default value for `number_integer_t` is `std::int64_t`.
|
||||
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default value for `number_unsigned_t` is `std::uint64_t`.
|
||||
With the default values for *NumberFloatType* (`#!cpp double`), the default value for `number_float_t` is `#!cpp double`.
|
||||
With the default values for *NumberIntegerType* (`std::int64_t`), the default type for `number_integer_t` is `std::int64_t`.
|
||||
With the default values for *NumberUnsignedType* (`std::uint64_t`), the default type for `number_unsigned_t` is `std::uint64_t`.
|
||||
With the default values for *NumberFloatType* (`#!cpp double`), the default type for `number_float_t` is `#!cpp double`.
|
||||
|
||||
### Default behavior
|
||||
|
||||
@@ -281,7 +281,7 @@ As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_
|
||||
|
||||
> This specification allows implementations to set limits on the range and precision of numbers accepted. Since software that implements IEEE 754-2008 binary64 (double precision) numbers is generally available and widely used, good interoperability can be achieved by implementations that expect no more precision or range than these provide, in the sense that implementations will approximate JSON numbers within the expected precision.
|
||||
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note that numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
|
||||
This implementation does exactly follow this approach, as it uses double precision floating-point numbers by default (see *NumberFloatType* above). Note that with the default `#!cpp double`, numbers smaller than `#!c -1.79769313486232e+308` or greater than `#!c 1.79769313486232e+308` are rejected during parsing with [`out_of_range.406`](../../home/exceptions.md#jsonexceptionout_of_range406). Non-finite values (NaN and infinity) can only be stored by assigning them directly and are serialized to `#!json null`.
|
||||
|
||||
### Storage
|
||||
|
||||
|
||||
@@ -345,8 +345,11 @@ The number types can be changed with template parameters.
|
||||
in case of overflow.
|
||||
- The type for unsigned integers must be convertible from `#!c unsigned long long`. The type for floating-point
|
||||
numbers is used in case of overflow.
|
||||
- The types for signed and unsigned integers must be distinct, see
|
||||
[#2573](https://github.com/nlohmann/json/issues/2573).
|
||||
- The type for unsigned integers can also be the same signed type as the type for signed integers (e.g.,
|
||||
`#!c std::int64_t` for both). Then every integer is stored as a signed integer, and
|
||||
[`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!c false`, see
|
||||
[Template Parameter Requirements](template_parameters.md#same-type-for-both).
|
||||
- The type for floating-point numbers must be distinct from the integer types.
|
||||
- Only `#!c double`, `#!c float`, and `#!c long double` are supported for floating-point numbers.
|
||||
|
||||
!!! example
|
||||
|
||||
@@ -40,19 +40,19 @@ Requirements are split into two groups:
|
||||
|
||||
## Overview
|
||||
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|-----------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType` |
|
||||
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
|
||||
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
|
||||
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
|
||||
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
|
||||
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
|
||||
| Template parameter | Default | Notable substitutes |
|
||||
|-------------------------------------------------------------------|-----------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| [`ObjectType`](#objecttype) | `std::map` | [`nlohmann::ordered_map`](../../api/ordered_map.md), Abseil hash maps |
|
||||
| [`ArrayType`](#arraytype) | `std::vector` | `#!cpp std::deque` |
|
||||
| [`StringType`](#stringtype) | `std::string` | `std::string`-like types over `char` |
|
||||
| [`BooleanType`](#booleantype) | `bool` | none worth using |
|
||||
| [`NumberIntegerType`](#numberintegertype-and-numberunsignedtype) | `std::int64_t` | any signed integer type |
|
||||
| [`NumberUnsignedType`](#numberintegertype-and-numberunsignedtype) | `std::uint64_t` | any unsigned integer type at least as wide as `NumberIntegerType`, or `NumberIntegerType` itself |
|
||||
| [`NumberFloatType`](#numberfloattype) | `double` | `float` (`long double`: no binary formats) |
|
||||
| [`AllocatorType`](#allocatortype) | `std::allocator` | stateless allocators |
|
||||
| [`JSONSerializer`](#jsonserializer) | `adl_serializer` | serializers with the same interface |
|
||||
| [`BinaryType`](#binarytype) | `#!cpp std::vector<std::uint8_t>` | `#!cpp std::vector<char>` |
|
||||
| [`CustomBaseClass`](#custombaseclass) | `void` | any default-constructible class |
|
||||
|
||||
!!! warning "Third-party containers and incomplete types"
|
||||
|
||||
@@ -504,13 +504,14 @@ Both types are stored **directly** inside `basic_json`'s union.
|
||||
### Always required
|
||||
|
||||
- `#!cpp std::is_integral` must be satisfied: `NumberIntegerType` must be a **signed** integer type,
|
||||
`NumberUnsignedType` an **unsigned** integer type. Class types are not supported -- among others, the constructors
|
||||
taking integer values are constrained on `#!cpp std::is_integral`.
|
||||
`NumberUnsignedType` an **unsigned** integer type or the same type as `NumberIntegerType` (see
|
||||
[below](#same-type-for-both)). Class types are not supported -- among others, the constructors taking integer values
|
||||
are constrained on `#!cpp std::is_integral`.
|
||||
- Trivially default-constructible, trivially copyable, and trivially destructible (union member).
|
||||
- `#!cpp std::numeric_limits` must be specialized for both types.
|
||||
- `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value; serialization
|
||||
of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to be at least as
|
||||
wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
|
||||
- An unsigned `NumberUnsignedType` must be able to represent the absolute value of every `NumberIntegerType` value;
|
||||
serialization of negative numbers converts the value to `NumberUnsignedType`. A `#!cpp static_assert` requires it to
|
||||
be at least as wide as `NumberIntegerType`, which is what that amounts to for the standard integer types.
|
||||
- Both types must fit into the internal 64-character number buffer used by
|
||||
[`dump`](../../api/basic_json/dump.md), which is the case for all standard integer types.
|
||||
- [`std::hash<basic_json>`](../../api/basic_json/std_hash.md) additionally requires `#!cpp std::hash` specializations.
|
||||
@@ -522,6 +523,23 @@ is stored as [`number_float_t`](../../api/basic_json/number_float_t.md) instead.
|
||||
therefore silently changes parse results rather than raising an error. See
|
||||
[Number Handling](number_handling.md) for details.
|
||||
|
||||
#### Same type for both
|
||||
|
||||
`NumberUnsignedType` may be the same type as `NumberIntegerType`, e.g.
|
||||
`#!cpp nlohmann::json::with_integers_t<std::int64_t, std::int64_t>` (see
|
||||
[`with_integers_t`](../../api/basic_json/with_t.md)), for code that only wants to handle one integer type. Such a `basic_json` stores every integer as
|
||||
[`number_integer_t`](../../api/basic_json/number_integer_t.md):
|
||||
|
||||
- [`is_number_unsigned()`](../../api/basic_json/is_number_unsigned.md) is always `#!cpp false`, and
|
||||
[`type()`](../../api/basic_json/type.md) never returns `value_t::number_unsigned`. Constructing a value from
|
||||
`value_t::number_unsigned` gives a `value_t::number_integer` value.
|
||||
- Values of unsigned C++ types (e.g., `#!cpp 5u`) are converted to `NumberIntegerType`, so values above its maximum
|
||||
wrap around, as values outside the range of a narrower `NumberIntegerType` already do.
|
||||
- The parsers store a non-negative integer above the maximum of `NumberIntegerType` (e.g., `18446744073709551615` for
|
||||
`#!cpp std::int64_t`) as [`number_float_t`](../../api/basic_json/number_float_t.md), like any other integer that does
|
||||
not fit.
|
||||
- Converting from a `basic_json` with distinct types turns its unsigned values into `number_integer_t` values.
|
||||
|
||||
### Compatible types
|
||||
|
||||
| Type pair | Support |
|
||||
@@ -529,8 +547,9 @@ therefore silently changes parse results rather than raising an error. See
|
||||
| `#!cpp std::int64_t` / `#!cpp std::uint64_t` (default) | full |
|
||||
| `#!cpp std::int32_t` / `#!cpp std::uint32_t`, `#!cpp long long` / `#!cpp unsigned long long` | full; narrower types change which literals the parser can represent |
|
||||
| any other pair of standard signed/unsigned integer types | full |
|
||||
| the same signed integer type for both, e.g. `#!cpp std::int64_t` / `#!cpp std::int64_t` | full; every integer is stored as `number_integer_t`, see [above](#same-type-for-both) |
|
||||
| class types, enumerations | not usable; `#!cpp std::is_integral` must hold |
|
||||
| `#!cpp bool`, or a type already used for another member of the union | not usable; `#!cpp std::is_integral<bool>` is in fact `#!cpp true`, but the `get_impl_ptr` overloads for `boolean_t`, `number_integer_t`, `number_unsigned_t` and `number_float_t` would collide |
|
||||
| `#!cpp bool`, `number_float_t`, or the type of another member of the union | not usable; `#!cpp std::is_integral<bool>` is in fact `#!cpp true`, but the `get_impl_ptr` overloads for `boolean_t`, `number_integer_t`, `number_unsigned_t` and `number_float_t` would collide |
|
||||
|
||||
## `NumberFloatType`
|
||||
|
||||
|
||||
@@ -9,6 +9,15 @@ published on the [GitHub releases page](https://github.com/nlohmann/json/release
|
||||
This documentation is built from the `develop` branch and may describe changes that are not part of a release
|
||||
yet. Their version numbers are followed by an <span class="unreleased-version">unreleased</span> badge.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Converting an rvalue container to JSON (`json j = std::move(vec);`, `json j = std::map<std::string, std::string>{...};`)
|
||||
now **moves** the elements instead of copying them (e.g., `std::vector<std::string>` with 100,000 strings: 4.7 ms to
|
||||
2.3 ms). Two consequences: the source container is left in a valid but unspecified (moved-from) state, and if an
|
||||
exception is thrown during the conversion, the source may be partially moved-from, so the strong exception guarantee
|
||||
no longer holds for the source of an rvalue conversion (the JSON value itself is unaffected). Lvalues and `const`
|
||||
objects are still copied. See [`basic_json`](../api/basic_json/basic_json.md).
|
||||
|
||||
## v3.12.0 (2025-04-11)
|
||||
|
||||
Fixes bugs found in 3.11.3 and adds several features. All changes are backward-compatible.
|
||||
|
||||
@@ -80,7 +80,11 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
@@ -150,7 +154,7 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_flo
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)
|
||||
inline void from_json(const BasicJsonType& j, number_unsigned_overload_t<BasicJsonType>& val)
|
||||
{
|
||||
get_arithmetic_value(j, val);
|
||||
}
|
||||
@@ -266,6 +270,22 @@ template<typename ConstructibleArrayType>
|
||||
void from_json_array_reserve(ConstructibleArrayType& /*arr*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
// append through push_back() where available; containers without it (such as
|
||||
// std::set) fall back to insert(end(), ...), which is what std::inserter did
|
||||
template<typename ConstructibleArrayType, typename ValueType>
|
||||
auto from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<1> /*unused*/)
|
||||
-> decltype(arr.push_back(std::forward<ValueType>(value)), void())
|
||||
{
|
||||
arr.push_back(std::forward<ValueType>(value));
|
||||
}
|
||||
|
||||
template<typename ConstructibleArrayType, typename ValueType>
|
||||
void from_json_array_append(ConstructibleArrayType& arr, ValueType&& value, priority_tag<0> /*unused*/)
|
||||
{
|
||||
using std::end;
|
||||
arr.insert(end(arr), std::forward<ValueType>(value));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleArrayType,
|
||||
enable_if_t<
|
||||
std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,
|
||||
@@ -275,17 +295,15 @@ auto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, p
|
||||
j.template get<typename ConstructibleArrayType::value_type>(),
|
||||
void())
|
||||
{
|
||||
using std::end;
|
||||
|
||||
ConstructibleArrayType ret;
|
||||
from_json_array_reserve(ret, j.size(), priority_tag<1> {});
|
||||
std::transform(j.begin(), j.end(),
|
||||
std::inserter(ret, end(ret)), [](const BasicJsonType & i)
|
||||
const auto* inner_array = j.template get_ptr<const typename BasicJsonType::array_t*>();
|
||||
from_json_array_reserve(ret, inner_array->size(), priority_tag<1> {});
|
||||
for (const auto& elem : *inner_array)
|
||||
{
|
||||
// get<BasicJsonType>() returns *this, this won't call a from_json
|
||||
// method when value_type is BasicJsonType
|
||||
return i.template get<typename ConstructibleArrayType::value_type>();
|
||||
});
|
||||
from_json_array_append(ret, elem.template get<typename ConstructibleArrayType::value_type>(), priority_tag<1> {});
|
||||
}
|
||||
arr = std::move(ret);
|
||||
}
|
||||
|
||||
@@ -371,6 +389,23 @@ template<typename ConstructibleObjectType>
|
||||
inline void from_json_object_reserve(ConstructibleObjectType& /*obj*/, std::size_t /*size*/, priority_tag<0> /*unused*/)
|
||||
{}
|
||||
|
||||
// the keys of the source are sorted for the usual object types, so hinting the
|
||||
// end makes every insertion constant-time (for containers that order their keys
|
||||
// the same way); containers without emplace_hint() (such as ordered_map) fall
|
||||
// back to emplace()
|
||||
template<typename ConstructibleObjectType, typename Key, typename Value>
|
||||
auto from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<1> /*unused*/)
|
||||
-> decltype(obj.emplace_hint(obj.end(), std::forward<Key>(key), std::forward<Value>(value)), void())
|
||||
{
|
||||
obj.emplace_hint(obj.end(), std::forward<Key>(key), std::forward<Value>(value));
|
||||
}
|
||||
|
||||
template<typename ConstructibleObjectType, typename Key, typename Value>
|
||||
void from_json_object_emplace(ConstructibleObjectType& obj, Key&& key, Value&& value, priority_tag<0> /*unused*/)
|
||||
{
|
||||
obj.emplace(std::forward<Key>(key), std::forward<Value>(value));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename ConstructibleObjectType,
|
||||
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
|
||||
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
@@ -385,7 +420,7 @@ inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
|
||||
from_json_object_reserve(ret, inner_object->size(), priority_tag<1> {});
|
||||
for (const auto& p : *inner_object)
|
||||
{
|
||||
ret.emplace(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());
|
||||
from_json_object_emplace(ret, p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>(), priority_tag<1> {});
|
||||
}
|
||||
obj = std::move(ret);
|
||||
}
|
||||
@@ -406,7 +441,11 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val)
|
||||
{
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());
|
||||
// never null: a basic_json whose number_unsigned_t is number_integer_t
|
||||
// stores no number_unsigned values (#2573)
|
||||
const auto* ptr = j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>();
|
||||
JSON_ASSERT(ptr != nullptr);
|
||||
val = static_cast<ArithmeticType>(*ptr);
|
||||
break;
|
||||
}
|
||||
case value_t::number_integer:
|
||||
|
||||
@@ -167,6 +167,17 @@ struct external_constructor<value_t::array>
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
/// an rvalue container: move its elements
|
||||
template<typename BasicJsonType, typename CompatibleArrayType>
|
||||
static void construct_moving(BasicJsonType& j, CompatibleArrayType& arr)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::array_t>(std::make_move_iterator(begin(arr)), std::make_move_iterator(end(arr))));
|
||||
j.replace_value(value_t::array, value);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static void construct(BasicJsonType& j, const std::vector<bool>& arr)
|
||||
{
|
||||
@@ -217,6 +228,17 @@ struct external_constructor<value_t::object>
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
/// an rvalue container: move its values
|
||||
template<typename BasicJsonType, typename CompatibleObjectType>
|
||||
static void construct_moving(BasicJsonType& j, CompatibleObjectType& obj)
|
||||
{
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const typename BasicJsonType::json_value value(j.template create<typename BasicJsonType::object_t>(std::make_move_iterator(begin(obj)), std::make_move_iterator(end(obj))));
|
||||
j.replace_value(value_t::object, value);
|
||||
}
|
||||
|
||||
template < typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >
|
||||
static void construct(BasicJsonType& j, const CompatibleObjectType& obj)
|
||||
@@ -303,14 +325,14 @@ inline void to_json(BasicJsonType& j, FloatType val) noexcept
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberUnsignedType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_integer_type<number_unsigned_overload_t<BasicJsonType>, CompatibleNumberUnsignedType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType,
|
||||
enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
enable_if_t<is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept
|
||||
{
|
||||
external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));
|
||||
@@ -354,6 +376,66 @@ inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
|
||||
external_constructor<value_t::array>::construct(j, arr);
|
||||
}
|
||||
|
||||
// types that have a dedicated to_json overload and must not be treated as generic containers
|
||||
template<typename T>
|
||||
struct has_dedicated_to_json : std::false_type {};
|
||||
|
||||
#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM
|
||||
template<>
|
||||
struct has_dedicated_to_json<std_fs::path> : std::true_type {};
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_lib_char8_t)
|
||||
template<typename Tr, typename Allocator>
|
||||
struct has_dedicated_to_json<std::basic_string<char8_t, Tr, Allocator>> : std::true_type {};
|
||||
#endif
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
template<typename T>
|
||||
struct is_range_view_type : is_compatible_range_view<T> {};
|
||||
#else
|
||||
template<typename T>
|
||||
struct is_range_view_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
#if JSON_USE_OBJECTS_FOR_ENUM_KEYED_MAPS
|
||||
template<typename T>
|
||||
struct is_enum_keyed_map_type : is_enum_keyed_map<T> {};
|
||||
#else
|
||||
template<typename T>
|
||||
struct is_enum_keyed_map_type : std::false_type {};
|
||||
#endif
|
||||
|
||||
// rvalue (non-const) container: the generic to_json takes its elements by move
|
||||
template<typename T>
|
||||
using is_movable_container_rvalue = negation<disjunction<
|
||||
std::is_lvalue_reference<T>,
|
||||
std::is_const<T>,
|
||||
has_dedicated_to_json<T>,
|
||||
is_basic_json<T>,
|
||||
is_range_view_type<T>,
|
||||
is_enum_keyed_map_type<T>,
|
||||
std::is_same<T, std::vector<bool>>>>;
|
||||
|
||||
// rvalue array-like containers
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_array_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
is_compatible_array_type<BasicJsonType, T>,
|
||||
negation<disjunction<
|
||||
std::is_same<T, typename BasicJsonType::array_t>,
|
||||
std::is_same<T, typename BasicJsonType::binary_t>,
|
||||
is_compatible_object_type<BasicJsonType, T>,
|
||||
is_compatible_string_type<BasicJsonType, T>,
|
||||
is_compatible_binary_type<BasicJsonType, T>>>>;
|
||||
|
||||
// rvalue containers: their elements are moved instead of copied
|
||||
template<typename BasicJsonType, typename CompatibleArrayType,
|
||||
enable_if_t<is_rvalue_array_to_json<BasicJsonType, CompatibleArrayType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleArrayType && arr)
|
||||
{
|
||||
external_constructor<value_t::array>::construct_moving(j, arr);
|
||||
}
|
||||
|
||||
#if JSON_HAS_RANGE_VIEW_CONVERSION
|
||||
template < typename BasicJsonType, typename T,
|
||||
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
|
||||
@@ -427,6 +509,19 @@ inline void to_json(BasicJsonType& j, const EnumKeyedMap& map)
|
||||
}
|
||||
#endif
|
||||
|
||||
// rvalue object-like containers
|
||||
template<typename BasicJsonType, typename T>
|
||||
using is_rvalue_object_to_json = conjunction<is_movable_container_rvalue<T>,
|
||||
negation<std::is_same<T, typename BasicJsonType::object_t>>,
|
||||
is_compatible_object_type<BasicJsonType, T>>;
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleObjectType,
|
||||
enable_if_t<is_rvalue_object_to_json<BasicJsonType, CompatibleObjectType>::value, int> = 0>
|
||||
inline void to_json(BasicJsonType& j, CompatibleObjectType && obj)
|
||||
{
|
||||
external_constructor<value_t::object>::construct_moving(j, obj);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)
|
||||
{
|
||||
|
||||
@@ -184,7 +184,10 @@ JSON_HEDLEY_ALWAYS_INLINE shortest_decimal to_shortest(std::uint64_t bits) noexc
|
||||
const uint128_parts p10 = pow10(-dec_exp - 1);
|
||||
const uint128_parts p = umul192_hi128(p10.high, p10.low, bin_sig << static_cast<unsigned>(shift));
|
||||
std::uint64_t integral = p.high >> static_cast<unsigned>(extra_shift);
|
||||
const std::uint64_t fractional = (p.high << static_cast<unsigned>(64 - extra_shift)) | (p.low >> static_cast<unsigned>(extra_shift));
|
||||
// (no casts in the shifts: MSVC 2015 warns C4554 on them despite the parentheses)
|
||||
constexpr unsigned fractional_low_shift = static_cast<unsigned>(extra_shift);
|
||||
constexpr unsigned fractional_high_shift = 64u - fractional_low_shift;
|
||||
const std::uint64_t fractional = (p.high << fractional_high_shift) | (p.low >> fractional_low_shift);
|
||||
std::uint64_t digit = 0;
|
||||
bool round_up = false;
|
||||
bool round_down = false;
|
||||
|
||||
@@ -428,7 +428,7 @@ void templated_json_throw(ExceptionType exception)
|
||||
@throw type_error.302 always
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const BasicJsonType& j)
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const char* expected, const BasicJsonType& j)
|
||||
{
|
||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
@@ -443,7 +443,7 @@ JSON_HEDLEY_NO_RETURN inline void throw_type_must_be(const char* expected, const
|
||||
@throw type_error always
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j)
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_use_with(const exception_id id_, const char* operation, const BasicJsonType& j)
|
||||
{
|
||||
static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(operation);
|
||||
@@ -451,6 +451,77 @@ JSON_HEDLEY_NO_RETURN inline void throw_cannot_use_with(const exception_id id_,
|
||||
JSON_THROW(type_error::create(id_, concat("cannot use ", operation, " with ", j.type_name()), &j));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws ExceptionType with a fixed message; kept out of line so that callers do not carry the exception construction
|
||||
*/
|
||||
template<typename ExceptionType, typename BasicJsonContext>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_literal(const exception_id id_, const char* what_arg, BasicJsonContext context)
|
||||
{
|
||||
static_cast<void>(id_); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(what_arg);
|
||||
static_cast<void>(context);
|
||||
JSON_THROW(ExceptionType::create(id_, what_arg, context));
|
||||
}
|
||||
|
||||
// JSON Patch errors; constructed out of line so that patch_inplace() does not carry them
|
||||
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_add_parent(const BasicJsonType& parent)
|
||||
{
|
||||
static_cast<void>(parent);
|
||||
JSON_THROW(out_of_range::create(exception_id::patch_add_parent_not_container, concat("cannot add value: the JSON Patch 'add' target's parent is of type ", parent.type_name(), ", but must be an object or array"), &parent));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_remove_parent(const BasicJsonType& parent)
|
||||
{
|
||||
static_cast<void>(parent);
|
||||
JSON_THROW(out_of_range::create(exception_id::patch_remove_parent_not_container, 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));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_not_an_array(const BasicJsonType& j)
|
||||
{
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(parse_error::create(exception_id::patch_not_an_array, 0, "JSON patch must be an array of objects", &j));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_missing_member(const StringType& op, const StringType& member, const bool string_type, const BasicJsonType& val)
|
||||
{
|
||||
static_cast<void>(op);
|
||||
static_cast<void>(member);
|
||||
static_cast<void>(string_type);
|
||||
static_cast<void>(val);
|
||||
// context-sensitive error message
|
||||
const std::string error_msg = (op == "op") ? std::string("operation") : concat("operation '", op, '\'');
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat(error_msg, string_type ? " must have string member '" : " must have member '", member, "'"), &val));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_move_into_child(const StringType& from_path, const StringType& path, const BasicJsonType& result)
|
||||
{
|
||||
static_cast<void>(from_path);
|
||||
static_cast<void>(path);
|
||||
static_cast<void>(result);
|
||||
JSON_THROW(out_of_range::create(exception_id::patch_move_into_child, concat("cannot move value: 'from' path '", from_path, "' is a proper prefix of 'path' '", path, "'"), &result));
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_test_failed(const BasicJsonType& val)
|
||||
{
|
||||
static_cast<void>(val);
|
||||
JSON_THROW(other_error::create(exception_id::patch_test_failed, concat("unsuccessful: ", val.dump()), &val));
|
||||
}
|
||||
|
||||
template<typename StringType, typename BasicJsonType>
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_patch_invalid_operation(const StringType& op, const BasicJsonType& val)
|
||||
{
|
||||
static_cast<void>(op);
|
||||
static_cast<void>(val);
|
||||
JSON_THROW(parse_error::create(exception_id::patch_invalid_operation, 0, concat("operation value '", op, "' is invalid"), &val));
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ class binary_reader
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof()))
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_end_of_input, concat("expected end of input; last byte: 0x", get_token_string()), "value");
|
||||
return last_byte_error(exception_id::unexpected_end_of_input, "expected end of input", "value", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,7 @@ class binary_reader
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document");
|
||||
return bson_document_size_error(document_size, chars_read - document_start);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -509,7 +509,7 @@ class binary_reader
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(len < 1))
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("string length must be at least 1, is ", std::to_string(len)), "string");
|
||||
return bson_length_error("string length must be at least 1, is ", len, "string");
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_string(len - static_cast<NumberType>(1), result)))
|
||||
@@ -539,7 +539,7 @@ class binary_reader
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(len < 0))
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("byte array length cannot be negative, is ", std::to_string(len)), "binary");
|
||||
return bson_length_error("byte array length cannot be negative, is ", len, "binary");
|
||||
}
|
||||
|
||||
// All BSON binary values have a subtype
|
||||
@@ -629,9 +629,7 @@ class binary_reader
|
||||
|
||||
default: // anything else is not supported (yet)
|
||||
{
|
||||
const std::string cr_str = hex_byte(static_cast<std::uint8_t>(element_type));
|
||||
return sax->parse_error(element_type_parse_position, cr_str,
|
||||
parse_error::create(exception_id::bson_unsupported_type, element_type_parse_position, concat("Unsupported BSON record type 0x", cr_str), nullptr));
|
||||
return bson_unsupported_type_error(element_type_parse_position, element_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1060,6 +1058,7 @@ class binary_reader
|
||||
@param[in] context parsing context for the error message
|
||||
@return whether the SAX consumer accepts the parse error
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool cbor_indefinite_string_error(const char* type_name, const char* context)
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("indefinite-length ", type_name, " is not allowed inside indefinite-length ", type_name, "; last byte: 0x", get_token_string()), context);
|
||||
@@ -1139,7 +1138,7 @@ class binary_reader
|
||||
|
||||
default:
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x60-0x7B)", inside_indefinite ? "" : " or indefinite string type (0x7F)", "; last byte: 0x", get_token_string()), "string");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "expected length specification (0x60-0x7B)", "string", inside_indefinite ? "" : " or indefinite string type (0x7F)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1269,7 +1268,7 @@ class binary_reader
|
||||
break;
|
||||
}
|
||||
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1350,7 +1349,7 @@ class binary_reader
|
||||
|
||||
default:
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0x40-0x5B)", inside_indefinite ? "" : " or indefinite binary array type (0x5F)", "; last byte: 0x", get_token_string()), "binary");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "expected length specification (0x40-0x5B)", "binary", inside_indefinite ? "" : " or indefinite binary array type (0x5F)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1496,8 +1495,7 @@ class binary_reader
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
|
||||
exception_message(concat("excessive ", context, " size"), "size"), nullptr));
|
||||
return container_size_error(context);
|
||||
}
|
||||
result = conditional_static_cast<std::size_t>(len);
|
||||
return true;
|
||||
@@ -2065,7 +2063,7 @@ class binary_reader
|
||||
|
||||
default:
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x", get_token_string()), "string");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "expected length specification (0xA0-0xBF, 0xD9-0xDB)", "string", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2157,7 +2155,7 @@ class binary_reader
|
||||
break;
|
||||
}
|
||||
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("only string keys are supported, but found ", found, "; last byte: 0x", get_token_string()), "object key");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "only string keys are supported, but found ", "object key", found);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -2655,8 +2653,7 @@ class binary_reader
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(number)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
|
||||
exception_message("integer value overflow", "size"), nullptr));
|
||||
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
|
||||
}
|
||||
result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char
|
||||
return true;
|
||||
@@ -2753,8 +2750,7 @@ class binary_reader
|
||||
}
|
||||
if (!value_in_range_of<std::size_t>(number))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
|
||||
exception_message("integer value overflow", "size"), nullptr));
|
||||
return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size");
|
||||
}
|
||||
result = detail::conditional_static_cast<std::size_t>(number);
|
||||
return true;
|
||||
@@ -2832,7 +2828,7 @@ class binary_reader
|
||||
// or SIZE_MAX.
|
||||
if (JSON_HEDLEY_UNLIKELY(result > (std::numeric_limits<std::size_t>::max)() / i))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr));
|
||||
return out_of_range_error(exception_id::container_too_large, "excessive ndarray size caused overflow", "size");
|
||||
}
|
||||
result *= i;
|
||||
// the pre-check above already rules out result becoming 0
|
||||
@@ -2841,7 +2837,7 @@ class binary_reader
|
||||
// unknown-size container (see get_ubjson_size_type())
|
||||
if (result == npos)
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large, exception_message("excessive ndarray size caused overflow", "size"), nullptr));
|
||||
return out_of_range_error(exception_id::container_too_large, "excessive ndarray size caused overflow", "size");
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!emit_unsigned(i)))
|
||||
{
|
||||
@@ -2891,7 +2887,7 @@ class binary_reader
|
||||
if (input_format == input_format_t::bjdata
|
||||
&& JSON_HEDLEY_UNLIKELY(is_bjd_excluded_optimized_type(result.second)))
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type");
|
||||
return optimized_type_error();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof("type")))
|
||||
@@ -2906,7 +2902,7 @@ class binary_reader
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("expected '#' after type information; last byte: 0x", get_token_string()), "size");
|
||||
return last_byte_error(exception_id::unexpected_byte, "expected '#' after type information", "size", "");
|
||||
}
|
||||
|
||||
const bool is_error = get_ubjson_size_value(result.first, is_ndarray, 0, result.second);
|
||||
@@ -3057,7 +3053,7 @@ class binary_reader
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(current > 127))
|
||||
{
|
||||
return last_byte_error(exception_id::invalid_string_or_size, concat("byte after 'C' must be in range 0x00..0x7F; last byte: 0x", get_token_string()), "char");
|
||||
return last_byte_error(exception_id::invalid_string_or_size, "byte after 'C' must be in range 0x00..0x7F", "char", "");
|
||||
}
|
||||
string_t s(1, static_cast<typename string_t::value_type>(current));
|
||||
return sax->string(s);
|
||||
@@ -3141,8 +3137,7 @@ class binary_reader
|
||||
if (JSON_HEDLEY_UNLIKELY((size_and_type.second == 'Z' || size_and_type.second == 'T' || size_and_type.second == 'F')
|
||||
&& size_and_type.first > max_valueless_container_size))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
|
||||
exception_message("excessive array size", "size"), nullptr));
|
||||
return out_of_range_error(exception_id::container_too_large, "excessive array size", "size");
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!enter_array(size_and_type.first, size_and_type.second)))
|
||||
@@ -3854,7 +3849,7 @@ class binary_reader
|
||||
{
|
||||
// in case of failure, advance position by 1 to report the failing location
|
||||
++chars_read;
|
||||
sax->parse_error(chars_read, "<end of file>", parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr));
|
||||
static_cast<void>(report_eof(context));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -4007,8 +4002,7 @@ class binary_reader
|
||||
const auto result = static_cast<number_float_t>(number);
|
||||
if (JSON_HEDLEY_UNLIKELY(std::isfinite(number) && !std::isfinite(result)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(),
|
||||
out_of_range::create(exception_id::number_overflow, exception_message("number overflow", "value"), nullptr));
|
||||
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
||||
}
|
||||
return sax->number_float(result, "");
|
||||
}
|
||||
@@ -4223,22 +4217,82 @@ class binary_reader
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof()))
|
||||
{
|
||||
return sax->parse_error(chars_read, "<end of file>",
|
||||
parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr));
|
||||
return report_eof(context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reports an unexpected end of input (out of line to keep the exception
|
||||
construction out of the hot reading functions)
|
||||
@param[in] context further context information (for diagnostics)
|
||||
@return the result of the SAX parser's parse_error()
|
||||
*/
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool report_eof(const char* context) const
|
||||
{
|
||||
return sax->parse_error(chars_read, "<end of file>",
|
||||
parse_error::create(exception_id::unexpected_end_of_input, chars_read, exception_message("unexpected end of input", context), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reports an out_of_range error at the last read byte (out of line)
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool out_of_range_error(const exception_id id_, const char* detail, const char* context) const
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(id_, exception_message(detail, context), nullptr));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool container_size_error(const char* what) const
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(exception_id::container_too_large,
|
||||
exception_message(concat("excessive ", what, " size"), "size"), nullptr));
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool optimized_type_error() const
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("marker 0x", get_token_string(), " is not a permitted optimized array type"), "type");
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool bson_length_error(const char* prefix, const NumberType len, const char* context) const
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat(prefix, std::to_string(len)), context);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool bson_document_size_error(const std::int32_t document_size, const std::size_t bytes_read) const
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(bytes_read), ")"), "document");
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool bson_unsupported_type_error(const std::size_t position, const char element_type) const
|
||||
{
|
||||
const std::string cr_str = hex_byte(static_cast<std::uint8_t>(element_type));
|
||||
return sax->parse_error(position, cr_str,
|
||||
parse_error::create(exception_id::bson_unsupported_type, position, concat("Unsupported BSON record type 0x", cr_str), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reports a parse error at the last read byte
|
||||
@param[in] id_ the id of the parse_error exception
|
||||
@param[in] detail a detailed error message
|
||||
@param[in] context further context information
|
||||
@param[in] tail optional text appended to the detail, followed by "; last byte: 0x<token>"
|
||||
@return the result of the SAX parser's parse_error()
|
||||
*/
|
||||
bool last_byte_error(const exception_id id_, const std::string& detail, const std::string& context) const
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool last_byte_error(const exception_id id_, const std::string& detail, const char* context, const char* tail = nullptr) const
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read, exception_message(detail, context), nullptr));
|
||||
// with a tail, the message reads "<detail><tail>; last byte: 0x<token>"
|
||||
return sax->parse_error(chars_read, get_token_string(), parse_error::create(id_, chars_read,
|
||||
exception_message(tail == nullptr ? detail : concat(detail, tail, "; last byte: 0x", get_token_string()), context), nullptr));
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4247,7 +4301,8 @@ class binary_reader
|
||||
@param[in] context further context information
|
||||
@return the result of the SAX parser's parse_error()
|
||||
*/
|
||||
bool unexpected_byte(const char* detail, const std::string& context) const
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool unexpected_byte(const char* detail, const char* context) const
|
||||
{
|
||||
return last_byte_error(exception_id::unexpected_byte, concat(detail, ": 0x", get_token_string()), context);
|
||||
}
|
||||
@@ -4258,6 +4313,7 @@ class binary_reader
|
||||
@param[in] context further context information
|
||||
@return the result of the SAX parser's parse_error()
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool length_type_error(const char* position, const char* context) const
|
||||
{
|
||||
const char* types = input_format == input_format_t::bjdata ? "U, i, u, I, m, l, M, L" : "U, i, I, l, L";
|
||||
|
||||
@@ -187,12 +187,19 @@ Shared by json_sax_dom_parser and json_sax_dom_callback_parser.
|
||||
@param[in] ref the object or array that was just created
|
||||
@return whether parsing should continue (false after reporting out_of_range.408)
|
||||
*/
|
||||
template<typename SAX, typename BasicJsonType>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool container_too_large_error(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref)
|
||||
{
|
||||
return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref));
|
||||
}
|
||||
|
||||
template<typename SAX, typename BasicJsonType>
|
||||
bool check_container_size(SAX& sax, std::size_t len, const char* kind, BasicJsonType* ref)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref->max_size()))
|
||||
{
|
||||
return sax.parse_error(0, "", out_of_range::create(exception_id::container_too_large, concat("excessive ", kind, " size: ", std::to_string(len)), ref));
|
||||
return container_too_large_error(sax, len, kind, ref);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -674,16 +681,19 @@ class json_sax_dom_callback_parser
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
const auto it = obj.find(val);
|
||||
if (it != obj.end())
|
||||
// a single lookup: insert the placeholder, or find the existing member
|
||||
const auto inserted = obj.emplace(val, discarded);
|
||||
const auto it = inserted.first;
|
||||
if (!inserted.second)
|
||||
{
|
||||
// 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);
|
||||
it->second = discarded;
|
||||
}
|
||||
object_element = &(obj[val] = discarded);
|
||||
object_element = &(it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <cstring> // memcpy
|
||||
#include <limits> // numeric_limits
|
||||
#include <string> // string
|
||||
#include <type_traits> // conditional, integral_constant, true_type, false_type
|
||||
#include <type_traits> // conditional, integral_constant, is_signed, true_type, false_type
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
@@ -50,6 +50,20 @@ NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// whether an integer of a signed type is negative; the overload for other types
|
||||
// avoids comparing an unsigned value with zero
|
||||
template<typename NumberType>
|
||||
constexpr bool is_negative_integer(NumberType x, std::true_type /*is_signed*/) noexcept
|
||||
{
|
||||
return x < 0;
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
constexpr bool is_negative_integer(NumberType /*unused*/, std::false_type /*is_signed*/) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief fast integer parser for an already-validated unsigned integer
|
||||
|
||||
@@ -83,8 +97,10 @@ bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedT
|
||||
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;
|
||||
// reject values that do not round-trip into a narrower NumberUnsignedType,
|
||||
// and values that wrap around to negative numbers if it is a signed type
|
||||
// (number_unsigned_t may be the same type as number_integer_t, #2573)
|
||||
return static_cast<std::uint64_t>(value) == x && !is_negative_integer(value, std::is_signed<NumberUnsignedType>());
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -704,7 +720,7 @@ inline void float_from_bits(std::uint64_t bits, float& value) noexcept
|
||||
{
|
||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||
const auto bits32 = static_cast<std::uint32_t>(bits);
|
||||
std::memcpy(&value, &bits32, sizeof(value));
|
||||
std::memcpy(&value, &bits32, sizeof(bits32));
|
||||
}
|
||||
|
||||
/// the powers of ten that are exact in binary64 (up to 10^22)
|
||||
|
||||
@@ -280,7 +280,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
}
|
||||
|
||||
/// @throw invalid_iterator.214 always
|
||||
JSON_HEDLEY_NO_RETURN void throw_cannot_get_value() const
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_cannot_get_value() const
|
||||
{
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_value_unavailable, "cannot get value", m_object));
|
||||
}
|
||||
@@ -294,40 +294,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||
return m_it.object_iterator->second;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||
return *m_it.array_iterator;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
throw_cannot_get_value();
|
||||
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
||||
{
|
||||
return *m_object;
|
||||
}
|
||||
|
||||
throw_cannot_get_value();
|
||||
}
|
||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||
return *m_it.array_iterator;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||
{
|
||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||
return m_it.object_iterator->second;
|
||||
}
|
||||
|
||||
// neither array nor object: a primitive value (null has no value to point to)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type != value_t::null && m_it.primitive_iterator.is_begin()))
|
||||
{
|
||||
return *m_object;
|
||||
}
|
||||
|
||||
throw_cannot_get_value();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -338,38 +323,25 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||
return &(m_it.object_iterator->second);
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||
return &*m_it.array_iterator;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
||||
{
|
||||
return m_object;
|
||||
}
|
||||
|
||||
throw_cannot_get_value();
|
||||
}
|
||||
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
|
||||
return &*m_it.array_iterator;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||
{
|
||||
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
|
||||
return &(m_it.object_iterator->second);
|
||||
}
|
||||
|
||||
// neither array nor object: a primitive value
|
||||
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
|
||||
{
|
||||
return m_object;
|
||||
}
|
||||
|
||||
throw_cannot_get_value();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -391,35 +363,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
std::advance(m_it.object_iterator, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
std::advance(m_it.array_iterator, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
++m_it.primitive_iterator;
|
||||
break;
|
||||
}
|
||||
++m_it.array_iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||
{
|
||||
std::advance(m_it.object_iterator, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// neither array nor object: a primitive value
|
||||
++m_it.primitive_iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -442,35 +399,20 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
std::advance(m_it.object_iterator, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
std::advance(m_it.array_iterator, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
{
|
||||
--m_it.primitive_iterator;
|
||||
break;
|
||||
}
|
||||
--m_it.array_iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||
{
|
||||
std::advance(m_it.object_iterator, -1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// neither array nor object: a primitive value
|
||||
--m_it.primitive_iterator;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -484,7 +426,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
// if objects are not the same, the comparison is undefined
|
||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||
{
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||
}
|
||||
|
||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||
@@ -493,25 +435,18 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (m_object->m_data.m_type)
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::array))
|
||||
{
|
||||
case value_t::object:
|
||||
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||
|
||||
case value_t::array:
|
||||
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||
|
||||
case value_t::null:
|
||||
case value_t::string:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
||||
return (m_it.array_iterator == other.m_it.array_iterator);
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_LIKELY(m_object->m_data.m_type == value_t::object))
|
||||
{
|
||||
return (m_it.object_iterator == other.m_it.object_iterator);
|
||||
}
|
||||
|
||||
// neither array nor object: a primitive value
|
||||
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -533,7 +468,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
// if objects are not the same, the comparison is undefined
|
||||
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
|
||||
{
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterators_compare_different_values, "cannot compare iterators of different containers", m_object);
|
||||
}
|
||||
|
||||
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
|
||||
@@ -546,7 +481,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
switch (m_object->m_data.m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_order_on_object, "cannot compare order of object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
return (m_it.array_iterator < other.m_it.array_iterator);
|
||||
@@ -602,7 +537,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
switch (m_object->m_data.m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
@@ -681,7 +616,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
switch (m_object->m_data.m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_arithmetic_on_object, "cannot use offsets with object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
return m_it.array_iterator - other.m_it.array_iterator;
|
||||
@@ -710,7 +645,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
switch (m_object->m_data.m_type)
|
||||
{
|
||||
case value_t::object:
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_subscript_on_object, "cannot use operator[] for object iterators", m_object);
|
||||
|
||||
case value_t::array:
|
||||
return *std::next(m_it.array_iterator, n);
|
||||
@@ -750,7 +685,7 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
return m_it.object_iterator->first;
|
||||
}
|
||||
|
||||
JSON_THROW(invalid_iterator::create(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object));
|
||||
throw_literal<invalid_iterator>(exception_id::iterator_key_not_object, "cannot use key() for non-object iterators", m_object);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -16,14 +16,13 @@
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
#include <limits> // max
|
||||
#include <map> // map
|
||||
#include <numeric> // accumulate
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
@@ -69,12 +68,21 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/to_string/
|
||||
string_t to_string() const
|
||||
{
|
||||
return std::accumulate(reference_tokens.begin(), reference_tokens.end(),
|
||||
string_t{},
|
||||
[](const string_t& a, const string_t& b)
|
||||
// one buffer: every token adds its escaped form (at least its length) and a slash
|
||||
std::size_t length = 0;
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
return detail::concat<string_t>(a, '/', detail::escape(b));
|
||||
});
|
||||
length += reference_token.size() + 1;
|
||||
}
|
||||
|
||||
string_t result;
|
||||
result.reserve(length);
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
result.append("/", 1);
|
||||
detail::escape_into(result, reference_token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief return a string representation of the JSON pointer
|
||||
@@ -129,21 +137,29 @@ class json_pointer
|
||||
friend json_pointer operator/(const json_pointer& lhs,
|
||||
const json_pointer& rhs)
|
||||
{
|
||||
return json_pointer(lhs) /= rhs;
|
||||
json_pointer result;
|
||||
result.reference_tokens.reserve(lhs.reference_tokens.size() + rhs.reference_tokens.size());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), rhs.reference_tokens.begin(), rhs.reference_tokens.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)
|
||||
{
|
||||
return json_pointer(lhs) /= std::move(token);
|
||||
json_pointer result;
|
||||
result.reference_tokens.reserve(lhs.reference_tokens.size() + 1);
|
||||
result.reference_tokens.insert(result.reference_tokens.end(), lhs.reference_tokens.begin(), lhs.reference_tokens.end());
|
||||
result.reference_tokens.push_back(std::move(token));
|
||||
return result;
|
||||
}
|
||||
|
||||
/// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/
|
||||
friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)
|
||||
{
|
||||
return json_pointer(lhs) /= array_idx;
|
||||
return lhs / detail::to_string<string_t>(array_idx);
|
||||
}
|
||||
|
||||
/// @brief returns the parent of this JSON pointer
|
||||
@@ -155,8 +171,8 @@ class json_pointer
|
||||
return *this;
|
||||
}
|
||||
|
||||
json_pointer res = *this;
|
||||
res.pop_back();
|
||||
json_pointer res;
|
||||
res.reference_tokens = std::vector<string_t>(reference_tokens.begin(), reference_tokens.end() - 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -245,14 +261,14 @@ class json_pointer
|
||||
|
||||
private:
|
||||
/// @throw out_of_range.405 always
|
||||
JSON_HEDLEY_NO_RETURN static void throw_no_parent()
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_no_parent()
|
||||
{
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::patch_on_root, "JSON pointer has no parent", nullptr));
|
||||
}
|
||||
|
||||
/// @throw out_of_range.404 always
|
||||
template<typename BasicJsonContext>
|
||||
JSON_HEDLEY_NO_RETURN static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE static void throw_unresolved(const string_t& reference_token, BasicJsonContext context)
|
||||
{
|
||||
static_cast<void>(reference_token); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(context);
|
||||
@@ -305,6 +321,28 @@ class json_pointer
|
||||
return array_index_status::not_a_number;
|
||||
}
|
||||
|
||||
// fast path: up to 18 decimal digits cannot overflow unsigned long long or
|
||||
// size_type (9 digits for a 32-bit size_type), so strtoull and errno are not needed
|
||||
constexpr std::size_t max_fast_digits = sizeof(size_type) >= 8 ? 18 : 9;
|
||||
if (!s.empty() && s.size() <= max_fast_digits)
|
||||
{
|
||||
size_type acc = 0;
|
||||
for (std::size_t i = 0; i < s.size(); ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (JSON_HEDLEY_UNLIKELY(c < '0' || c > '9'))
|
||||
{
|
||||
break; // not a plain number: let strtoull decide
|
||||
}
|
||||
acc = static_cast<size_type>(acc * 10 + static_cast<size_type>(c - '0'));
|
||||
if (JSON_HEDLEY_UNLIKELY(i == s.size() - 1))
|
||||
{
|
||||
idx = acc;
|
||||
return array_index_status::ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* p = s.data();
|
||||
char* p_end = nullptr; // NOLINT(misc-const-correctness)
|
||||
errno = 0; // strtoull doesn't reset errno
|
||||
@@ -361,6 +399,83 @@ class json_pointer
|
||||
return idx;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief compare with a JSON pointer string without splitting it into tokens
|
||||
|
||||
@throw parse_error.107 if the string is not empty or begins with '/'
|
||||
@throw parse_error.108 if character '~' is not followed by '0' or '1'
|
||||
*/
|
||||
bool equals_string(const string_t& reference_string) const
|
||||
{
|
||||
if (reference_string.empty())
|
||||
{
|
||||
return reference_tokens.empty();
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
|
||||
// the whole string is validated, whether or not it matches
|
||||
const std::size_t size = reference_string.size();
|
||||
for (std::size_t pos = reference_string.find_first_of('~');
|
||||
pos != string_t::npos;
|
||||
pos = reference_string.find_first_of('~', pos + 1))
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == size - 1 || (reference_string[pos + 1] != '0' && reference_string[pos + 1] != '1')))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
// compare token by token, unescaping the string on the fly
|
||||
std::size_t pos = 0; // the slash in front of the next token
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
if (pos >= size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::size_t i = pos + 1;
|
||||
std::size_t j = 0;
|
||||
const std::size_t token_size = reference_token.size();
|
||||
while (i < size && reference_string[i] != '/')
|
||||
{
|
||||
char c = reference_string[i++];
|
||||
if (c == '~')
|
||||
{
|
||||
c = (reference_string[i++] == '0') ? '~' : '/';
|
||||
}
|
||||
if (j == token_size || reference_token[j] != c)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
if (j != token_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pos = i;
|
||||
}
|
||||
return pos == size;
|
||||
}
|
||||
|
||||
/// @brief object member access with a key that is only copied if it is inserted
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_member(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
auto& members = *object.m_data.m_value.object;
|
||||
const auto it = members.find(key);
|
||||
if (it != members.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
json_pointer top() const
|
||||
{
|
||||
@@ -384,84 +499,141 @@ class json_pointer
|
||||
the result independent of the order in which the flattened object is
|
||||
iterated, which is unspecified for some object types.
|
||||
|
||||
The prefixes form a tree and are numbered, so each of them is stored only
|
||||
once (as a node) rather than as a copy of all of its reference tokens.
|
||||
The prefixes form a tree and are numbered (number 0 is the empty prefix),
|
||||
so each of them is stored only once (as a node) rather than as a copy of
|
||||
all of its reference tokens. The tree does not own the reference tokens:
|
||||
it refers to the tokens of the parsed keys, which must outlive it.
|
||||
*/
|
||||
struct prefix_tree
|
||||
{
|
||||
// children[id] maps a reference token to the number of the prefix
|
||||
// extended by that token; number 0 is the empty prefix
|
||||
std::vector<std::map<string_t, std::size_t>> children;
|
||||
// open-addressing hash table from (parent, last reference token) to
|
||||
// the number of the node; empty slots have token == nullptr
|
||||
struct slot
|
||||
{
|
||||
std::size_t hash;
|
||||
std::size_t parent;
|
||||
const string_t* token;
|
||||
std::size_t id;
|
||||
|
||||
slot() noexcept
|
||||
: hash(0), parent(0), token(nullptr), id(0)
|
||||
{}
|
||||
|
||||
slot(std::size_t h, std::size_t p, const string_t* t, std::size_t i) noexcept
|
||||
: hash(h), parent(p), token(t), id(i)
|
||||
{}
|
||||
};
|
||||
|
||||
std::vector<slot> slots;
|
||||
std::size_t used = 0;
|
||||
|
||||
static std::size_t hash_of(std::size_t parent, const string_t& token) noexcept
|
||||
{
|
||||
std::size_t h = parent * static_cast<std::size_t>(0x9E3779B97F4A7C15ULL);
|
||||
for (std::size_t i = 0; i < token.size(); ++i)
|
||||
{
|
||||
h = (h ^ static_cast<unsigned char>(token[i])) * static_cast<std::size_t>(0x100000001B3ULL);
|
||||
}
|
||||
return h ^ (h >> 29u);
|
||||
}
|
||||
|
||||
void grow()
|
||||
{
|
||||
std::vector<slot> old(slots.size() * 2);
|
||||
old.swap(slots);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
for (const auto& s : old)
|
||||
{
|
||||
if (s.token != nullptr)
|
||||
{
|
||||
std::size_t i = s.hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
slots[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parent[id] is the number of the prefix without its last token
|
||||
std::vector<std::size_t> parent;
|
||||
// is_array[id] is true iff some flattened key has the reference token
|
||||
// 0 directly below the prefix with number id
|
||||
std::vector<bool> is_array;
|
||||
|
||||
// start with the empty prefix only
|
||||
prefix_tree()
|
||||
: children(1)
|
||||
: slots(16)
|
||||
, parent(1, 0)
|
||||
, is_array(1, false)
|
||||
{}
|
||||
|
||||
// return the number of the prefix with number id extended by
|
||||
// reference_token, adding it if it is new
|
||||
std::size_t add_child(std::size_t id, string_t&& reference_token)
|
||||
std::size_t add_child(std::size_t id, const string_t& reference_token)
|
||||
{
|
||||
const std::size_t hash = hash_of(id, reference_token);
|
||||
const std::size_t mask = slots.size() - 1;
|
||||
std::size_t i = hash & mask;
|
||||
while (slots[i].token != nullptr)
|
||||
{
|
||||
if (slots[i].hash == hash && slots[i].parent == id && *slots[i].token == reference_token)
|
||||
{
|
||||
return slots[i].id;
|
||||
}
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
|
||||
// new prefix
|
||||
const std::size_t child = parent.size();
|
||||
slots[i] = slot(hash, id, &reference_token, child);
|
||||
if (reference_token == "0")
|
||||
{
|
||||
is_array[id] = true;
|
||||
}
|
||||
parent.push_back(id);
|
||||
is_array.push_back(false);
|
||||
|
||||
// read the number before the emplace_back below, which may
|
||||
// reallocate children and invalidate the iterator
|
||||
const std::size_t next = children.size();
|
||||
const auto inserted = children[id].emplace(std::move(reference_token), next);
|
||||
const std::size_t child = inserted.first->second;
|
||||
if (inserted.second)
|
||||
// keep the load factor below 1/2
|
||||
if (++used * 2 > slots.size())
|
||||
{
|
||||
children.emplace_back();
|
||||
is_array.push_back(false);
|
||||
grow();
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
// return the number of the prefix with number id extended by
|
||||
// reference_token, which must have been added before
|
||||
std::size_t find_child(std::size_t id, const string_t& reference_token) const
|
||||
{
|
||||
const auto it = children[id].find(reference_token);
|
||||
JSON_ASSERT(it != children[id].end());
|
||||
return it->second;
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief create and return a reference to the pointed to value
|
||||
|
||||
Complexity: Linear in the number of reference tokens (times the logarithm
|
||||
of the number of siblings for the prefix lookup).
|
||||
@param[in] j the value to create the pointed to value in
|
||||
@param[in] tokens the reference tokens of the pointer
|
||||
@param[in] prefixes prefixes[i] is the number in @a tree of the prefix
|
||||
consisting of the first i reference tokens
|
||||
|
||||
Complexity: Linear in the number of reference tokens.
|
||||
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if array index is not a number
|
||||
@throw type_error.313 if value cannot be unflattened
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_and_create(BasicJsonType& j, const prefix_tree& tree) const
|
||||
static BasicJsonType& get_and_create(BasicJsonType& j, const std::vector<string_t>& tokens,
|
||||
const std::vector<std::size_t>& prefixes, const prefix_tree& tree)
|
||||
{
|
||||
auto* result = &j;
|
||||
|
||||
// the number of the prefix consumed so far; used to look up whether
|
||||
// the value to be created below is an array or an object
|
||||
std::size_t id = 0;
|
||||
|
||||
// in case no reference tokens exist, return a reference to the JSON value
|
||||
// j which will be overwritten by a primitive value
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
for (std::size_t i = 0; i < tokens.size(); ++i)
|
||||
{
|
||||
const auto& reference_token = tokens[i];
|
||||
switch (result->type())
|
||||
{
|
||||
case detail::value_t::null:
|
||||
{
|
||||
if (tree.is_array[id])
|
||||
if (tree.is_array[prefixes[i]])
|
||||
{
|
||||
// some reference token below this position is 0, so the
|
||||
// value is an array
|
||||
@@ -478,7 +650,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// create an entry in the object
|
||||
result = &result->operator[](reference_token);
|
||||
result = &object_member(*result, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -505,8 +677,6 @@ class json_pointer
|
||||
default:
|
||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_invalid_value, "invalid value to unflatten", &j));
|
||||
}
|
||||
|
||||
id = tree.find_child(id, reference_token);
|
||||
}
|
||||
|
||||
return *result;
|
||||
@@ -558,7 +728,7 @@ class json_pointer
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// use unchecked object access
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -608,8 +778,13 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
// note: at performs range check
|
||||
ptr = &ptr->at(reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
// at() throws out_of_range.403 with the usual message
|
||||
ptr->at(reference_token);
|
||||
}
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -630,7 +805,7 @@ class json_pointer
|
||||
JSON_THROW(detail::out_of_range::create(detail::exception_id::array_index_out_of_range, detail::concat(
|
||||
"array index ", std::to_string(idx), " is out of range"), ptr));
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -678,7 +853,9 @@ class json_pointer
|
||||
{
|
||||
// use unchecked object access; the const operator[]
|
||||
// asserts that the key exists
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
JSON_ASSERT(it != ptr->m_data.m_value.object->end());
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -692,7 +869,9 @@ class json_pointer
|
||||
|
||||
// use unchecked array access; the const operator[]
|
||||
// asserts that the index exists
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
const auto idx = array_index<BasicJsonType>(reference_token);
|
||||
JSON_ASSERT(idx < ptr->m_data.m_value.array->size());
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -733,12 +912,12 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
const auto it = ptr->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->end()))
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (JSON_HEDLEY_UNLIKELY(it == ptr->m_data.m_value.object->end()))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &*it;
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -775,7 +954,7 @@ class json_pointer
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -808,13 +987,14 @@ class json_pointer
|
||||
{
|
||||
case detail::value_t::object:
|
||||
{
|
||||
if (!ptr->contains(reference_token))
|
||||
const auto it = ptr->m_data.m_value.object->find(reference_token);
|
||||
if (it == ptr->m_data.m_value.object->end())
|
||||
{
|
||||
// we did not find the key in the object
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](reference_token);
|
||||
ptr = &it->second;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -836,13 +1016,13 @@ class json_pointer
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idx >= ptr->size())
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// index out of range
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = &ptr->operator[](idx);
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -892,6 +1072,15 @@ class json_pointer
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_missing_slash, 1, detail::concat("JSON pointer must be empty or begin with '/' - was: '", reference_string, "'"), nullptr));
|
||||
}
|
||||
|
||||
// every slash starts a reference token
|
||||
const std::size_t size = reference_string.size();
|
||||
std::size_t token_count = 0;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
token_count += (reference_string[i] == '/') ? 1 : 0;
|
||||
}
|
||||
result.reserve(token_count);
|
||||
|
||||
// extract the reference tokens:
|
||||
// - slash: position of the last read slash (or end of string)
|
||||
// - start: position after the previous slash
|
||||
@@ -913,31 +1102,52 @@ class json_pointer
|
||||
const auto count = (slash == string_t::npos ? reference_string.size() : slash) - start;
|
||||
auto reference_token = string_t(reference_string.data() + start, count);
|
||||
|
||||
// check reference tokens are properly escaped
|
||||
for (std::size_t pos = reference_token.find_first_of('~');
|
||||
pos != string_t::npos;
|
||||
pos = reference_token.find_first_of('~', pos + 1))
|
||||
// check reference tokens are properly escaped; a token without
|
||||
// a '~' (the common case) needs neither check nor unescaping
|
||||
std::size_t pos = reference_token.find_first_of('~');
|
||||
if (pos != string_t::npos)
|
||||
{
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||
(reference_token[pos + 1] != '0' &&
|
||||
reference_token[pos + 1] != '1')))
|
||||
for (; pos != string_t::npos; pos = reference_token.find_first_of('~', pos + 1))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
JSON_ASSERT(reference_token[pos] == '~');
|
||||
|
||||
// ~ must be followed by 0 or 1
|
||||
if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||
|
||||
(reference_token[pos + 1] != '0' &&
|
||||
reference_token[pos + 1] != '1')))
|
||||
{
|
||||
JSON_THROW(detail::parse_error::create(detail::exception_id::pointer_invalid_escape, 0, "escape character '~' must be followed with '0' or '1'", nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
detail::unescape(reference_token);
|
||||
}
|
||||
|
||||
// finally, store the reference token
|
||||
detail::unescape(reference_token);
|
||||
result.push_back(reference_token);
|
||||
result.push_back(std::move(reference_token));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
// Add the entry (key, v) to the object `obj`. Flattened paths are pairwise
|
||||
// distinct, so objects that can append a key known to be absent
|
||||
// (ordered_map) skip the lookup, which is linear and would make flatten
|
||||
// quadratic; all other object types use operator[].
|
||||
template<typename ObjectType, typename ValueType>
|
||||
static auto add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(obj.emplace_new_key(key, std::forward<ValueType>(v)), void())
|
||||
{
|
||||
obj.emplace_new_key(key, std::forward<ValueType>(v));
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename ValueType>
|
||||
static void add_flattened(ObjectType& obj, const string_t& key, ValueType&& v, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
obj[key] = std::forward<ValueType>(v);
|
||||
}
|
||||
|
||||
/*!
|
||||
@param[in] reference_string the reference string to the current value
|
||||
@param[in] value the value to consider
|
||||
@@ -982,7 +1192,8 @@ class json_pointer
|
||||
|
||||
// flatten `v`, whose path is `path`: primitives and empty containers
|
||||
// are added to the result right away; other containers get a frame
|
||||
const auto enter = [&stack, &path, &result](const BasicJsonType & v)
|
||||
auto& object = *result.m_data.m_value.object;
|
||||
const auto enter = [&stack, &path, &object](const BasicJsonType & v)
|
||||
{
|
||||
switch (v.type())
|
||||
{
|
||||
@@ -991,7 +1202,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.array->empty())
|
||||
{
|
||||
// flatten empty array as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1005,7 +1216,7 @@ class json_pointer
|
||||
if (v.m_data.m_value.object->empty())
|
||||
{
|
||||
// flatten empty object as null
|
||||
result[path] = nullptr;
|
||||
add_flattened(object, path, nullptr, detail::priority_tag<1> {});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1025,7 +1236,7 @@ class json_pointer
|
||||
default:
|
||||
{
|
||||
// add a primitive value with its reference string
|
||||
result[path] = v;
|
||||
add_flattened(object, path, v, detail::priority_tag<1> {});
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1071,6 +1282,10 @@ class json_pointer
|
||||
enter(it->second);
|
||||
}
|
||||
}
|
||||
|
||||
// the entries were added without going through basic_json, so the
|
||||
// parent pointers (JSON_DIAGNOSTICS) have to be set now
|
||||
result.set_parents();
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -1094,21 +1309,53 @@ class json_pointer
|
||||
|
||||
BasicJsonType result;
|
||||
|
||||
// parse every key once; the reference tokens are kept for the second
|
||||
// pass (the vectors are never resized, so the tree may refer to them)
|
||||
std::vector<std::vector<string_t>> keys;
|
||||
keys.reserve(value.m_data.m_value.object->size());
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
keys.push_back(std::move(ptr.reference_tokens));
|
||||
}
|
||||
|
||||
// collect the pointer prefixes that have a reference token 0 among
|
||||
// their children; the values below them are arrays, all others are
|
||||
// objects (see prefix_tree)
|
||||
prefix_tree tree;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
// the number of the prefix with i + 1 tokens of the previous key; keys
|
||||
// are often sorted, so most of the prefix is the same as for the
|
||||
// previous key and need not be looked up again
|
||||
std::vector<std::size_t> path;
|
||||
const std::vector<string_t>* previous = nullptr;
|
||||
// the number of the complete prefix (i.e., the key) of each key
|
||||
std::vector<std::size_t> leaves;
|
||||
leaves.reserve(keys.size());
|
||||
for (const auto& tokens : keys)
|
||||
{
|
||||
json_pointer ptr(element.first);
|
||||
std::size_t id = 0;
|
||||
for (auto& reference_token : ptr.reference_tokens)
|
||||
std::size_t common = 0;
|
||||
if (previous != nullptr)
|
||||
{
|
||||
id = tree.add_child(id, std::move(reference_token));
|
||||
const std::size_t limit = (std::min)(tokens.size(), previous->size());
|
||||
while (common < limit && tokens[common] == (*previous)[common])
|
||||
{
|
||||
++common;
|
||||
}
|
||||
}
|
||||
path.resize(common);
|
||||
std::size_t id = common == 0 ? 0 : path[common - 1];
|
||||
for (std::size_t i = common; i < tokens.size(); ++i)
|
||||
{
|
||||
id = tree.add_child(id, tokens[i]);
|
||||
path.push_back(id);
|
||||
}
|
||||
leaves.push_back(id);
|
||||
previous = &tokens;
|
||||
}
|
||||
|
||||
// iterate the JSON object values
|
||||
std::vector<std::size_t> prefixes;
|
||||
std::size_t key_index = 0;
|
||||
for (const auto& element : *value.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))
|
||||
@@ -1116,11 +1363,21 @@ class json_pointer
|
||||
JSON_THROW(detail::type_error::create(detail::exception_id::unflatten_value_not_primitive, "values in object must be primitive", &element.second));
|
||||
}
|
||||
|
||||
const auto& tokens = keys[key_index];
|
||||
// number the prefixes of this key from its last one to the first
|
||||
prefixes.resize(tokens.size());
|
||||
for (std::size_t i = tokens.size(), id = leaves[key_index]; i > 0; --i)
|
||||
{
|
||||
id = tree.parent[id];
|
||||
prefixes[i - 1] = id;
|
||||
}
|
||||
++key_index;
|
||||
|
||||
// Assign the value to the reference pointed to by JSON pointer. Note
|
||||
// that if the JSON pointer is "" (i.e., points to the whole value),
|
||||
// function get_and_create returns a reference to the result itself.
|
||||
// An assignment will then create a primitive value.
|
||||
json_pointer(element.first).get_and_create(result, tree) = element.second;
|
||||
get_and_create(result, tokens, prefixes, tree) = element.second;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1159,7 +1416,7 @@ class json_pointer
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return *this == json_pointer(rhs);
|
||||
return equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1243,7 +1500,7 @@ inline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return lhs == json_pointer<RefStringTypeLhs>(rhs);
|
||||
return lhs.equals_string(rhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1256,7 +1513,7 @@ inline bool operator==(const StringType& lhs,
|
||||
= delete;
|
||||
#else
|
||||
{
|
||||
return json_pointer<RefStringTypeRhs>(lhs) == rhs;
|
||||
return rhs.equals_string(lhs);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits> // numeric_limits
|
||||
#include <map> // map, multimap
|
||||
#include <string> // char_traits
|
||||
#include <tuple> // tuple
|
||||
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
|
||||
#include <unordered_map> // unordered_map, unordered_multimap
|
||||
#include <utility> // declval
|
||||
#include <vector> // vector
|
||||
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
|
||||
@@ -683,6 +685,22 @@ struct is_compatible_integer_type
|
||||
: is_compatible_integer_type_impl<RealIntegerType,
|
||||
CompatibleNumberIntegerType> {};
|
||||
|
||||
// whether to_json() stores an integer of type CompatibleNumberIntegerType as
|
||||
// number_integer
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType, typename = void>
|
||||
struct is_compatible_number_integer_type
|
||||
: is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
|
||||
|
||||
// if number_unsigned_t is the same type as number_integer_t, unsigned integers
|
||||
// are stored as number_integer as well (#2573)
|
||||
template<typename BasicJsonType, typename CompatibleNumberIntegerType>
|
||||
struct is_compatible_number_integer_type<BasicJsonType, CompatibleNumberIntegerType,
|
||||
enable_if_t<conjunction<std::is_same<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>,
|
||||
std::is_integral<CompatibleNumberIntegerType>,
|
||||
std::is_unsigned<CompatibleNumberIntegerType>,
|
||||
negation<std::is_same<bool, CompatibleNumberIntegerType>>>::value>>
|
||||
: std::is_constructible<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType> {};
|
||||
|
||||
template<typename BasicJsonType, typename CompatibleType, typename = void>
|
||||
struct is_compatible_type_impl: std::false_type {};
|
||||
|
||||
@@ -897,6 +915,31 @@ struct has_capacity : std::integral_constant<bool, is_detected<detect_capacity,
|
||||
template <typename T>
|
||||
struct is_ordered_map : has_capacity<T> {};
|
||||
|
||||
// whether T is a specialization of nlohmann::ordered_map, which only ever
|
||||
// appends members, so they can only move when its capacity changes
|
||||
template<typename T>
|
||||
struct is_nlohmann_ordered_map : std::false_type {};
|
||||
|
||||
template<class Key, class T, class IgnoredLess, class Allocator>
|
||||
struct is_nlohmann_ordered_map<ordered_map<Key, T, IgnoredLess, Allocator>> : std::true_type {};
|
||||
|
||||
// whether adding or erasing elements never moves the other elements of the
|
||||
// container T; only node-based standard containers are known to guarantee this
|
||||
template<typename T>
|
||||
struct has_stable_elements : std::false_type {};
|
||||
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
struct has_stable_elements<std::map<Key, T, Compare, Allocator>> : std::true_type {};
|
||||
|
||||
template<class Key, class T, class Compare, class Allocator>
|
||||
struct has_stable_elements<std::multimap<Key, T, Compare, Allocator>> : std::true_type {};
|
||||
|
||||
template<class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
struct has_stable_elements<std::unordered_map<Key, T, Hash, KeyEqual, Allocator>> : std::true_type {};
|
||||
|
||||
template<class Key, class T, class Hash, class KeyEqual, class Allocator>
|
||||
struct has_stable_elements<std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>> : std::true_type {};
|
||||
|
||||
// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)
|
||||
template < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 >
|
||||
T conditional_static_cast(U value)
|
||||
@@ -1020,6 +1063,25 @@ constexpr bool value_in_range_of(T val)
|
||||
template<bool Value>
|
||||
using bool_constant = std::integral_constant<bool, Value>;
|
||||
|
||||
// Stands in for number_unsigned_t in overload sets if it is the same type as
|
||||
// number_integer_t, so that the unsigned overloads do not collide with the
|
||||
// integer ones. Never constructed. Such a basic_json stores every integer as
|
||||
// number_integer (#2573).
|
||||
struct same_as_number_integer_type {};
|
||||
|
||||
// NumberUnsignedType, or same_as_number_integer_type if it is the same type as
|
||||
// NumberIntegerType; basic_json uses this one, as it cannot name its own member
|
||||
// types through an incomplete basic_json (MSVC reports a recursive alias, and
|
||||
// old GCC versions crash)
|
||||
template<typename NumberIntegerType, typename NumberUnsignedType>
|
||||
using number_unsigned_overload_type = typename std::conditional<std::is_same<NumberIntegerType, NumberUnsignedType>::value,
|
||||
same_as_number_integer_type, NumberUnsignedType>::type;
|
||||
|
||||
// number_unsigned_t of BasicJsonType, or same_as_number_integer_type if it is
|
||||
// the same type as number_integer_t
|
||||
template<typename BasicJsonType>
|
||||
using number_unsigned_overload_t = number_unsigned_overload_type<typename BasicJsonType::number_integer_t, typename BasicJsonType::number_unsigned_t>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// is_c_string
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -221,7 +221,7 @@ class binary_writer
|
||||
|
||||
case value_t::number_unsigned:
|
||||
{
|
||||
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||
write_cbor_head(0x00, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1601,7 +1601,7 @@ class binary_writer
|
||||
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||
return calc_bson_unsigned_size(conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return calc_bson_string_size(*j.m_data.m_value.string, j);
|
||||
@@ -1643,7 +1643,7 @@ class binary_writer
|
||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||
|
||||
case value_t::number_unsigned:
|
||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
||||
return write_bson_unsigned(name, conditional_static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
||||
|
||||
case value_t::string:
|
||||
return write_bson_string(name, *j.m_data.m_value.string);
|
||||
|
||||
@@ -49,6 +49,12 @@ class serializer
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
/// type of the absolute value of an integer: number_unsigned_t, or its
|
||||
/// unsigned counterpart if it is signed (it may be number_integer_t, #2573)
|
||||
using number_magnitude_t = typename std::conditional <
|
||||
std::is_integral<number_unsigned_t>::value && std::is_signed<number_unsigned_t>::value,
|
||||
std::make_unsigned<number_unsigned_t>,
|
||||
std::enable_if<true, number_unsigned_t >>::type::type;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
|
||||
public:
|
||||
@@ -772,6 +778,33 @@ class serializer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// A byte below 0x60 is a complete code point and needs no
|
||||
// UTF-8 decoding. The scanner stops only at a control
|
||||
// character, a quote, or a backslash in that range, so look up
|
||||
// its escape sequence and write all 8 bytes of the table entry
|
||||
// straight into the write buffer; only the length stored in the
|
||||
// last byte of the entry is kept.
|
||||
if (data[i] < 0x60)
|
||||
{
|
||||
// emit any bytes still pending in string_buffer first to
|
||||
// preserve output order
|
||||
if (bytes != 0)
|
||||
{
|
||||
put_buffer(string_buffer, bytes);
|
||||
bytes = 0;
|
||||
}
|
||||
const auto& escape = ascii_escapes()[data[i]];
|
||||
if (JSON_HEDLEY_UNLIKELY(write_buffer.size() - write_buffer_pos < escape.size()))
|
||||
{
|
||||
flush();
|
||||
}
|
||||
std::memcpy(write_buffer.data() + write_buffer_pos, escape.data(), escape.size());
|
||||
write_buffer_pos += static_cast<std::size_t>(escape[7]);
|
||||
bytes_after_last_accept = 0;
|
||||
undumped_chars = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||
@@ -780,92 +813,39 @@ class serializer
|
||||
{
|
||||
case UTF8_ACCEPT: // decode found a new code point
|
||||
{
|
||||
switch (codepoint)
|
||||
// the code point is at least 0x60 (smaller ones are handled
|
||||
// above) and is not printable ASCII (which the scanner
|
||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
||||
// character
|
||||
if (EnsureAscii)
|
||||
{
|
||||
case 0x08: // backspace
|
||||
JSON_ASSERT(codepoint >= 0x7F);
|
||||
if (error_handler == error_handler_t::keep)
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'b';
|
||||
break;
|
||||
// this character was buffered as raw bytes below
|
||||
// in case it turned out to be part of an
|
||||
// ill-formed sequence (which is kept as is); now
|
||||
// that it decoded to a well-formed code point,
|
||||
// undo that and \u-escape it like any other
|
||||
// character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
|
||||
case 0x09: // horizontal tab
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 't';
|
||||
break;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
|
||||
case 0x0A: // newline
|
||||
else
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'n';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0C: // formfeed
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'f';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x0D: // carriage return
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = 'r';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x22: // quotation mark
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '"';
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x5C: // reverse solidus
|
||||
{
|
||||
string_buffer[bytes++] = '\\';
|
||||
string_buffer[bytes++] = '\\';
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
// escape control characters (0x00..0x1F) or, if
|
||||
// EnsureAscii parameter is used, non-ASCII characters
|
||||
if ((codepoint <= 0x1F) || (EnsureAscii && (codepoint >= 0x7F)))
|
||||
{
|
||||
if (EnsureAscii && error_handler == error_handler_t::keep)
|
||||
{
|
||||
// this character was buffered as raw bytes
|
||||
// below in case it turned out to be part of
|
||||
// an ill-formed sequence (which is kept as
|
||||
// is); now that it decoded to a well-formed
|
||||
// code point, undo that and \u-escape it
|
||||
// like any other character instead
|
||||
bytes = bytes_after_last_accept;
|
||||
}
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes
|
||||
// been copied have in default case above)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
break;
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)));
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// copy byte to buffer (all previous bytes of the
|
||||
// sequence have been copied in the default case below)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
// write buffer and reset index; there must be 13 bytes
|
||||
// left, as this is the maximal number of bytes to be
|
||||
@@ -1227,7 +1207,7 @@ class serializer
|
||||
@param[in] x unsigned integer number to count its digits
|
||||
@return number of decimal digits
|
||||
*/
|
||||
unsigned int count_digits(number_unsigned_t x) noexcept
|
||||
unsigned int count_digits(number_magnitude_t x) noexcept
|
||||
{
|
||||
unsigned int n_digits = 1;
|
||||
for (;;)
|
||||
@@ -1253,6 +1233,121 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the JSON form of each character below 0x60
|
||||
|
||||
Entry c holds the escape sequence of character c - the short form for
|
||||
\\b, \\t, \\n, \\f, \\r, \\" and \\\\, \\u00xx for the other control characters -
|
||||
or c itself if it needs no escaping, padded with zeros to 7 bytes. The last
|
||||
byte is the length of the sequence. The 8 bytes of an entry are copied at
|
||||
once, so writing an escape needs neither a branch nor a byte-wise loop.
|
||||
*/
|
||||
static const std::array<std::array<char, 8>, 0x60>& ascii_escapes() noexcept
|
||||
{
|
||||
static const std::array<std::array<char, 8>, 0x60> escapes =
|
||||
{
|
||||
{
|
||||
{{'\\', 'u', '0', '0', '0', '0', 0, 6}}, // 0x00
|
||||
{{'\\', 'u', '0', '0', '0', '1', 0, 6}}, // 0x01
|
||||
{{'\\', 'u', '0', '0', '0', '2', 0, 6}}, // 0x02
|
||||
{{'\\', 'u', '0', '0', '0', '3', 0, 6}}, // 0x03
|
||||
{{'\\', 'u', '0', '0', '0', '4', 0, 6}}, // 0x04
|
||||
{{'\\', 'u', '0', '0', '0', '5', 0, 6}}, // 0x05
|
||||
{{'\\', 'u', '0', '0', '0', '6', 0, 6}}, // 0x06
|
||||
{{'\\', 'u', '0', '0', '0', '7', 0, 6}}, // 0x07
|
||||
{{'\\', 'b', 0, 0, 0, 0, 0, 2}}, // 0x08
|
||||
{{'\\', 't', 0, 0, 0, 0, 0, 2}}, // 0x09
|
||||
{{'\\', 'n', 0, 0, 0, 0, 0, 2}}, // 0x0A
|
||||
{{'\\', 'u', '0', '0', '0', 'b', 0, 6}}, // 0x0B
|
||||
{{'\\', 'f', 0, 0, 0, 0, 0, 2}}, // 0x0C
|
||||
{{'\\', 'r', 0, 0, 0, 0, 0, 2}}, // 0x0D
|
||||
{{'\\', 'u', '0', '0', '0', 'e', 0, 6}}, // 0x0E
|
||||
{{'\\', 'u', '0', '0', '0', 'f', 0, 6}}, // 0x0F
|
||||
{{'\\', 'u', '0', '0', '1', '0', 0, 6}}, // 0x10
|
||||
{{'\\', 'u', '0', '0', '1', '1', 0, 6}}, // 0x11
|
||||
{{'\\', 'u', '0', '0', '1', '2', 0, 6}}, // 0x12
|
||||
{{'\\', 'u', '0', '0', '1', '3', 0, 6}}, // 0x13
|
||||
{{'\\', 'u', '0', '0', '1', '4', 0, 6}}, // 0x14
|
||||
{{'\\', 'u', '0', '0', '1', '5', 0, 6}}, // 0x15
|
||||
{{'\\', 'u', '0', '0', '1', '6', 0, 6}}, // 0x16
|
||||
{{'\\', 'u', '0', '0', '1', '7', 0, 6}}, // 0x17
|
||||
{{'\\', 'u', '0', '0', '1', '8', 0, 6}}, // 0x18
|
||||
{{'\\', 'u', '0', '0', '1', '9', 0, 6}}, // 0x19
|
||||
{{'\\', 'u', '0', '0', '1', 'a', 0, 6}}, // 0x1A
|
||||
{{'\\', 'u', '0', '0', '1', 'b', 0, 6}}, // 0x1B
|
||||
{{'\\', 'u', '0', '0', '1', 'c', 0, 6}}, // 0x1C
|
||||
{{'\\', 'u', '0', '0', '1', 'd', 0, 6}}, // 0x1D
|
||||
{{'\\', 'u', '0', '0', '1', 'e', 0, 6}}, // 0x1E
|
||||
{{'\\', 'u', '0', '0', '1', 'f', 0, 6}}, // 0x1F
|
||||
{{' ', 0, 0, 0, 0, 0, 0, 1}}, // 0x20
|
||||
{{'!', 0, 0, 0, 0, 0, 0, 1}}, // 0x21
|
||||
{{'\\', '"', 0, 0, 0, 0, 0, 2}}, // 0x22
|
||||
{{'#', 0, 0, 0, 0, 0, 0, 1}}, // 0x23
|
||||
{{'$', 0, 0, 0, 0, 0, 0, 1}}, // 0x24
|
||||
{{'%', 0, 0, 0, 0, 0, 0, 1}}, // 0x25
|
||||
{{'&', 0, 0, 0, 0, 0, 0, 1}}, // 0x26
|
||||
{{'\'', 0, 0, 0, 0, 0, 0, 1}}, // 0x27
|
||||
{{'(', 0, 0, 0, 0, 0, 0, 1}}, // 0x28
|
||||
{{')', 0, 0, 0, 0, 0, 0, 1}}, // 0x29
|
||||
{{'*', 0, 0, 0, 0, 0, 0, 1}}, // 0x2A
|
||||
{{'+', 0, 0, 0, 0, 0, 0, 1}}, // 0x2B
|
||||
{{',', 0, 0, 0, 0, 0, 0, 1}}, // 0x2C
|
||||
{{'-', 0, 0, 0, 0, 0, 0, 1}}, // 0x2D
|
||||
{{'.', 0, 0, 0, 0, 0, 0, 1}}, // 0x2E
|
||||
{{'/', 0, 0, 0, 0, 0, 0, 1}}, // 0x2F
|
||||
{{'0', 0, 0, 0, 0, 0, 0, 1}}, // 0x30
|
||||
{{'1', 0, 0, 0, 0, 0, 0, 1}}, // 0x31
|
||||
{{'2', 0, 0, 0, 0, 0, 0, 1}}, // 0x32
|
||||
{{'3', 0, 0, 0, 0, 0, 0, 1}}, // 0x33
|
||||
{{'4', 0, 0, 0, 0, 0, 0, 1}}, // 0x34
|
||||
{{'5', 0, 0, 0, 0, 0, 0, 1}}, // 0x35
|
||||
{{'6', 0, 0, 0, 0, 0, 0, 1}}, // 0x36
|
||||
{{'7', 0, 0, 0, 0, 0, 0, 1}}, // 0x37
|
||||
{{'8', 0, 0, 0, 0, 0, 0, 1}}, // 0x38
|
||||
{{'9', 0, 0, 0, 0, 0, 0, 1}}, // 0x39
|
||||
{{':', 0, 0, 0, 0, 0, 0, 1}}, // 0x3A
|
||||
{{';', 0, 0, 0, 0, 0, 0, 1}}, // 0x3B
|
||||
{{'<', 0, 0, 0, 0, 0, 0, 1}}, // 0x3C
|
||||
{{'=', 0, 0, 0, 0, 0, 0, 1}}, // 0x3D
|
||||
{{'>', 0, 0, 0, 0, 0, 0, 1}}, // 0x3E
|
||||
{{'?', 0, 0, 0, 0, 0, 0, 1}}, // 0x3F
|
||||
{{'@', 0, 0, 0, 0, 0, 0, 1}}, // 0x40
|
||||
{{'A', 0, 0, 0, 0, 0, 0, 1}}, // 0x41
|
||||
{{'B', 0, 0, 0, 0, 0, 0, 1}}, // 0x42
|
||||
{{'C', 0, 0, 0, 0, 0, 0, 1}}, // 0x43
|
||||
{{'D', 0, 0, 0, 0, 0, 0, 1}}, // 0x44
|
||||
{{'E', 0, 0, 0, 0, 0, 0, 1}}, // 0x45
|
||||
{{'F', 0, 0, 0, 0, 0, 0, 1}}, // 0x46
|
||||
{{'G', 0, 0, 0, 0, 0, 0, 1}}, // 0x47
|
||||
{{'H', 0, 0, 0, 0, 0, 0, 1}}, // 0x48
|
||||
{{'I', 0, 0, 0, 0, 0, 0, 1}}, // 0x49
|
||||
{{'J', 0, 0, 0, 0, 0, 0, 1}}, // 0x4A
|
||||
{{'K', 0, 0, 0, 0, 0, 0, 1}}, // 0x4B
|
||||
{{'L', 0, 0, 0, 0, 0, 0, 1}}, // 0x4C
|
||||
{{'M', 0, 0, 0, 0, 0, 0, 1}}, // 0x4D
|
||||
{{'N', 0, 0, 0, 0, 0, 0, 1}}, // 0x4E
|
||||
{{'O', 0, 0, 0, 0, 0, 0, 1}}, // 0x4F
|
||||
{{'P', 0, 0, 0, 0, 0, 0, 1}}, // 0x50
|
||||
{{'Q', 0, 0, 0, 0, 0, 0, 1}}, // 0x51
|
||||
{{'R', 0, 0, 0, 0, 0, 0, 1}}, // 0x52
|
||||
{{'S', 0, 0, 0, 0, 0, 0, 1}}, // 0x53
|
||||
{{'T', 0, 0, 0, 0, 0, 0, 1}}, // 0x54
|
||||
{{'U', 0, 0, 0, 0, 0, 0, 1}}, // 0x55
|
||||
{{'V', 0, 0, 0, 0, 0, 0, 1}}, // 0x56
|
||||
{{'W', 0, 0, 0, 0, 0, 0, 1}}, // 0x57
|
||||
{{'X', 0, 0, 0, 0, 0, 0, 1}}, // 0x58
|
||||
{{'Y', 0, 0, 0, 0, 0, 0, 1}}, // 0x59
|
||||
{{'Z', 0, 0, 0, 0, 0, 0, 1}}, // 0x5A
|
||||
{{'[', 0, 0, 0, 0, 0, 0, 1}}, // 0x5B
|
||||
{{'\\', '\\', 0, 0, 0, 0, 0, 2}}, // 0x5C
|
||||
{{']', 0, 0, 0, 0, 0, 0, 1}}, // 0x5D
|
||||
{{'^', 0, 0, 0, 0, 0, 0, 1}}, // 0x5E
|
||||
{{'_', 0, 0, 0, 0, 0, 0, 1}}, // 0x5F
|
||||
}
|
||||
};
|
||||
return escapes;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief write a lowercase "\\uXXXX" escape sequence into @a string_buffer
|
||||
*
|
||||
@@ -1410,7 +1505,7 @@ class serializer
|
||||
}
|
||||
auto* buffer_ptr = write_buffer.data() + write_buffer_pos;
|
||||
|
||||
number_unsigned_t abs_value;
|
||||
number_magnitude_t abs_value;
|
||||
|
||||
// one byte for the minus sign
|
||||
unsigned int n_chars = 0;
|
||||
@@ -1423,7 +1518,7 @@ class serializer
|
||||
}
|
||||
else
|
||||
{
|
||||
abs_value = static_cast<number_unsigned_t>(x);
|
||||
abs_value = static_cast<number_magnitude_t>(x);
|
||||
}
|
||||
|
||||
// up to 16 digits: eight at a time (as the digits of floats), written
|
||||
@@ -1591,17 +1686,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload to make the compiler happy while it is instantiating
|
||||
* dump_integer for number_unsigned_t.
|
||||
* Must never be called.
|
||||
*/
|
||||
number_unsigned_t remove_sign(number_unsigned_t x)
|
||||
{
|
||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE
|
||||
return x; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for dump_integer
|
||||
*
|
||||
@@ -1611,10 +1695,10 @@ class serializer
|
||||
* absolute values of INT_MIN and INT_MAX are usually not the same. See
|
||||
* #1708 for details.
|
||||
*/
|
||||
number_unsigned_t remove_sign(number_integer_t x) noexcept
|
||||
number_magnitude_t remove_sign(number_integer_t x) noexcept
|
||||
{
|
||||
JSON_ASSERT(x < 0);
|
||||
return static_cast<number_unsigned_t>(-(x + 1)) + 1;
|
||||
return static_cast<number_magnitude_t>(-(x + 1)) + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <utility> // move
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
@@ -24,39 +25,46 @@ namespace detail
|
||||
* Note the order of escaping "~" to "~0" and "/" to "~1" is important.
|
||||
*
|
||||
* The string is rebuilt in a single pass, appending whole runs between the
|
||||
* characters that need escaping. Scanning with find_first_of() keeps the
|
||||
* common case -- nothing to escape -- as fast as a single search, while
|
||||
* repeated replace() calls would move the tail of the string once per
|
||||
* escaped character.
|
||||
* characters that need escaping, while repeated replace() calls would move
|
||||
* the tail of the string once per escaped character. @ref escape_into appends
|
||||
* the escaped string to an existing one.
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline void escape_into(StringType& out, const StringType& s)
|
||||
{
|
||||
const std::size_t size = s.size();
|
||||
std::size_t run = 0;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
const char c = s[i];
|
||||
if (c == '~' || c == '/')
|
||||
{
|
||||
out.append(s.data() + run, i - run);
|
||||
out.append(c == '~' ? "~0" : "~1", 2);
|
||||
run = i + 1;
|
||||
}
|
||||
}
|
||||
out.append(s.data() + run, size - run);
|
||||
}
|
||||
|
||||
template<typename StringType>
|
||||
inline StringType escape(const StringType& s)
|
||||
{
|
||||
auto next_special = [&s](std::size_t from)
|
||||
// a single scan for both special characters; nothing to escape is the common case
|
||||
const std::size_t size = s.size();
|
||||
std::size_t i = 0;
|
||||
while (i < size && s[i] != '~' && s[i] != '/')
|
||||
{
|
||||
const auto tilde = s.find_first_of('~', from);
|
||||
const auto slash = s.find_first_of('/', from);
|
||||
return tilde < slash ? tilde : slash; // npos is the largest value
|
||||
};
|
||||
|
||||
auto pos = next_special(0);
|
||||
if (pos == StringType::npos)
|
||||
++i;
|
||||
}
|
||||
if (i == size)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size() + 2);
|
||||
|
||||
std::size_t run = 0;
|
||||
while (pos != StringType::npos)
|
||||
{
|
||||
result.append(s.data() + run, pos - run);
|
||||
result.append(s[pos] == '~' ? "~0" : "~1", 2);
|
||||
run = pos + 1;
|
||||
pos = next_special(run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
result.reserve(size + 2);
|
||||
escape_into(result, s);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -101,7 +109,7 @@ inline void unescape(StringType& s)
|
||||
pos = s.find_first_of('~', run);
|
||||
}
|
||||
result.append(s.data() + run, s.size() - run);
|
||||
s = result;
|
||||
s = std::move(result);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -117,6 +117,34 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept
|
||||
}
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief whether a value of the given type owns heap memory
|
||||
|
||||
Strings, binaries, objects, and arrays are stored as pointers to allocated
|
||||
values; all other types are stored directly in the union. Every enumerator is
|
||||
listed so that -Wswitch-enum flags this function when a new type is added.
|
||||
*/
|
||||
inline bool owns_memory(const value_t t) noexcept
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
case value_t::string:
|
||||
case value_t::binary:
|
||||
return true;
|
||||
|
||||
case value_t::null:
|
||||
case value_t::boolean:
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*!
|
||||
@brief compare an integer with a floating point number without precision loss
|
||||
|
||||
+683
-312
File diff suppressed because it is too large.
Load diff
@@ -90,6 +90,15 @@ private:
|
||||
return self.end();
|
||||
}
|
||||
|
||||
/// @brief throw the exception of a failed at() lookup
|
||||
/// @note kept out of line so that at_impl() stays small enough to inline
|
||||
/// @throw std::out_of_range always
|
||||
JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE
|
||||
static void throw_key_not_found()
|
||||
{
|
||||
JSON_THROW(std::out_of_range("key not found"));
|
||||
}
|
||||
|
||||
/// @brief shared implementation of the const and non-const at() overloads
|
||||
/// @throw std::out_of_range if @a key is not found
|
||||
template<typename Self, typename KeyType>
|
||||
@@ -98,7 +107,7 @@ private:
|
||||
const auto it = find_impl(self, key);
|
||||
if (it == self.end())
|
||||
{
|
||||
JSON_THROW(std::out_of_range("key not found"));
|
||||
throw_key_not_found();
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
@@ -143,6 +152,17 @@ public:
|
||||
return {std::prev(this->end()), true};
|
||||
}
|
||||
|
||||
/// @brief add an element at the end without checking whether the key exists
|
||||
/// @note The caller must guarantee that @a key is not yet contained, otherwise
|
||||
/// the container holds duplicate keys. This avoids the linear search of
|
||||
/// emplace() and operator[] when many keys known to be new are added.
|
||||
template<class V, detail::enable_if_t<
|
||||
detail::is_constructible<T, V>::value, int> = 0>
|
||||
void emplace_new_key(const key_type& key, V && t)
|
||||
{
|
||||
append(key, std::forward<V>(t));
|
||||
}
|
||||
|
||||
T& operator[](const key_type& key)
|
||||
{
|
||||
return emplace(key, T{}).first->second;
|
||||
|
||||
Generated
+1796
-740
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,13 @@ Micro-benchmarks for parsing, serialization and the binary formats, written with
|
||||
| `FromBinaryBuffer`, `FromBinaryFile` | read CBOR, MessagePack, UBJSON, BJData and BSON from a buffer or a `FILE*` |
|
||||
| `FromBinaryShape` | read deeply nested, container-heavy and scalar-heavy documents in every binary format |
|
||||
| `FromCborChunkedString` | read CBOR strings split into indefinite-length chunks |
|
||||
| `SaxParse` | parse with a SAX handler that only counts events, so no values are built |
|
||||
| `Copy`, `Destroy`, `Equal`, `Iterate` | copy, destroy, or compare a parsed document, or visit every value with range-based `for` loops |
|
||||
| `Flatten`, `FlattenOrdered`, `Unflatten` | `flatten()` for `json` and `ordered_json`, and `unflatten()` |
|
||||
| `Diff`, `Patch` | `diff()` against the document with every eighth scalar changed, and applying that patch |
|
||||
| `JsonPointerParse`, `JsonPointerToString`, `JsonPointerAt`, `JsonPointerContains` | the JSON pointers of every scalar in a document: parse them from strings, turn them back into strings, look them up with `at()` and `contains()` |
|
||||
| `GetVectorInt`, `GetVectorDouble`, `GetMap` | `get<std::vector<std::int64_t>>()`, `get<std::vector<double>>()` and `get<std::map<std::string, json>>()` on large arrays and a large flat object |
|
||||
| `ToJsonStrings` | create a `json` array from a `std::vector` of 100,000 strings, copied or moved |
|
||||
|
||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||
|
||||
@@ -8,8 +8,14 @@
|
||||
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <test_data.hpp>
|
||||
|
||||
@@ -621,4 +627,547 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
||||
|
||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// SAX-parse JSON from string
|
||||
//
|
||||
// sax_parse() with a handler that does next to nothing runs the lexer and
|
||||
// parser without building values, so the gap to ParseString above is the cost
|
||||
// of building the DOM.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::string read_file(const char* filename)
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
static json load_json(const char* filename)
|
||||
{
|
||||
return json::parse(read_file(filename));
|
||||
}
|
||||
|
||||
/// SAX handler that only counts the events, so that the handler costs next to nothing
|
||||
class counting_sax : public nlohmann::json_sax<json>
|
||||
{
|
||||
public:
|
||||
std::size_t events = 0;
|
||||
|
||||
bool null() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool boolean(bool /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_integer(number_integer_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_unsigned(number_unsigned_t /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool number_float(number_float_t /*val*/, const string_t& /*s*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool string(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool binary(binary_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_object(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool key(string_t& /*val*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_object() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool start_array(std::size_t /*elements*/) override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool end_array() override
|
||||
{
|
||||
return count();
|
||||
}
|
||||
bool parse_error(std::size_t /*position*/, const std::string& /*last_token*/, const json::exception& /*ex*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool count()
|
||||
{
|
||||
++events;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static void SaxParse(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::string str = read_file(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
counting_sax sax;
|
||||
bool result = json::sax_parse(str, &sax);
|
||||
benchmark::DoNotOptimize(result);
|
||||
benchmark::DoNotOptimize(sax.events);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(state.iterations() * str.size());
|
||||
}
|
||||
BENCHMARK_CAPTURE(SaxParse, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(SaxParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(SaxParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// work with values: copy, destroy, compare, iterate
|
||||
//
|
||||
// The parsed test files stand in for a document an application keeps around.
|
||||
// Each benchmark times one whole-document operation; setup and cleanup run
|
||||
// outside the timed section.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void Copy(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto* copy = new json(j);
|
||||
|
||||
state.PauseTiming();
|
||||
delete copy;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Copy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Copy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Copy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Destroy(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
state.PauseTiming();
|
||||
auto* copy = new json(j);
|
||||
state.ResumeTiming();
|
||||
|
||||
delete copy;
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Destroy, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Destroy, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Destroy, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Equal(benchmark::State& state, const char* filename)
|
||||
{
|
||||
// two separate values, so that the comparison cannot stop at identical addresses
|
||||
const json j = load_json(filename);
|
||||
const json copy = j;
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
bool result = (j == copy);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Equal, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Equal, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Equal, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
/// visit every value with range-based for loops, as client code walks a document
|
||||
static std::size_t count_values(const json& j)
|
||||
{
|
||||
std::size_t result = 1;
|
||||
if (j.is_structured())
|
||||
{
|
||||
for (const auto& element : j)
|
||||
{
|
||||
result += count_values(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void Iterate(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
std::size_t result = count_values(j);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(count_values(j)));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Iterate, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Iterate, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Iterate, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// flatten and unflatten
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void Flatten(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json flat = j.flatten();
|
||||
benchmark::DoNotOptimize(flat);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Flatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Flatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Flatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
// ordered_json objects look keys up linearly, which flatten() used to do for
|
||||
// every value; canada (330,000 values in one flat object) is left out, as it
|
||||
// would take minutes with such versions
|
||||
static void FlattenOrdered(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const nlohmann::ordered_json j = nlohmann::ordered_json::parse(read_file(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
nlohmann::ordered_json flat = j.flatten();
|
||||
benchmark::DoNotOptimize(flat);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(FlattenOrdered, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(FlattenOrdered, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Unflatten(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json flat = load_json(filename).flatten();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json j = flat.unflatten();
|
||||
benchmark::DoNotOptimize(j);
|
||||
}
|
||||
}
|
||||
BENCHMARK_CAPTURE(Unflatten, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Unflatten, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Unflatten, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// diff and patch
|
||||
//
|
||||
// The target is the document with every eighth scalar changed, so the patch
|
||||
// has one "replace" operation per changed value and the structure stays the
|
||||
// same.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void change_every_eighth_scalar(json& j, std::size_t& counter)
|
||||
{
|
||||
if (j.is_structured())
|
||||
{
|
||||
for (auto& element : j)
|
||||
{
|
||||
change_every_eighth_scalar(element, counter);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (++counter % 8 != 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (j.type())
|
||||
{
|
||||
case json::value_t::number_integer:
|
||||
j = j.get<json::number_integer_t>() + 1;
|
||||
break;
|
||||
case json::value_t::number_unsigned:
|
||||
j = j.get<json::number_unsigned_t>() + 1;
|
||||
break;
|
||||
case json::value_t::number_float:
|
||||
j = j.get<json::number_float_t>() + 0.5;
|
||||
break;
|
||||
case json::value_t::string:
|
||||
j = j.get<std::string>() + "!";
|
||||
break;
|
||||
case json::value_t::boolean:
|
||||
j = !j.get<bool>();
|
||||
break;
|
||||
case json::value_t::null:
|
||||
case json::value_t::object:
|
||||
case json::value_t::array:
|
||||
case json::value_t::binary:
|
||||
case json::value_t::discarded:
|
||||
default:
|
||||
j = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static json changed_copy(const json& j)
|
||||
{
|
||||
json result = j;
|
||||
std::size_t counter = 0;
|
||||
change_every_eighth_scalar(result, counter);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void Diff(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json source = load_json(filename);
|
||||
const json target = changed_copy(source);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json patch = json::diff(source, target);
|
||||
benchmark::DoNotOptimize(patch);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(json::diff(source, target).size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Diff, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Diff, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Diff, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void Patch(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json source = load_json(filename);
|
||||
const json patch = json::diff(source, changed_copy(source));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
json result = source.patch(patch);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(patch.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(Patch, canada, TEST_DATA_DIRECTORY "/nativejson-benchmark/canada.json");
|
||||
BENCHMARK_CAPTURE(Patch, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(Patch, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// JSON Pointer
|
||||
//
|
||||
// The pointers are those of every scalar in the document, i.e., the keys of
|
||||
// its flattened form.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static std::vector<std::string> pointer_strings(const json& j)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
const json flat = j.flatten();
|
||||
result.reserve(flat.size());
|
||||
for (auto it = flat.begin(); it != flat.end(); ++it)
|
||||
{
|
||||
result.push_back(it.key());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::vector<json::json_pointer> pointers(const json& j)
|
||||
{
|
||||
const std::vector<std::string> strings = pointer_strings(j);
|
||||
std::vector<json::json_pointer> result;
|
||||
result.reserve(strings.size());
|
||||
for (const auto& s : strings)
|
||||
{
|
||||
result.emplace_back(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void JsonPointerParse(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::vector<std::string> strings = pointer_strings(load_json(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& s : strings)
|
||||
{
|
||||
json::json_pointer ptr(s);
|
||||
benchmark::DoNotOptimize(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerParse, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerParse, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerToString(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const std::vector<json::json_pointer> ptrs = pointers(load_json(filename));
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
std::string s = ptr.to_string();
|
||||
benchmark::DoNotOptimize(s);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerToString, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerToString, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerAt(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
const json& value = j.at(ptr);
|
||||
benchmark::DoNotOptimize(&value);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerAt, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerAt, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
static void JsonPointerContains(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
const std::vector<json::json_pointer> ptrs = pointers(j);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& ptr : ptrs)
|
||||
{
|
||||
bool result = j.contains(ptr);
|
||||
benchmark::DoNotOptimize(result);
|
||||
}
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(ptrs.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(JsonPointerContains, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
BENCHMARK_CAPTURE(JsonPointerContains, twitter, TEST_DATA_DIRECTORY "/nativejson-benchmark/twitter.json");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// conversions from and to C++ containers
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static void GetVectorInt(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto v = j.get<std::vector<std::int64_t>>();
|
||||
benchmark::DoNotOptimize(v);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetVectorInt, signed_ints, TEST_DATA_DIRECTORY "/regression/signed_ints.json");
|
||||
|
||||
static void GetVectorDouble(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto v = j.get<std::vector<double>>();
|
||||
benchmark::DoNotOptimize(v);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetVectorDouble, floats, TEST_DATA_DIRECTORY "/regression/floats.json");
|
||||
|
||||
// a large flat object: the flattened document, one member per scalar
|
||||
static void GetMap(benchmark::State& state, const char* filename)
|
||||
{
|
||||
const json j = load_json(filename).flatten();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto m = j.get<std::map<std::string, json>>();
|
||||
benchmark::DoNotOptimize(m);
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(j.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(GetMap, citm_catalog, TEST_DATA_DIRECTORY "/nativejson-benchmark/citm_catalog.json");
|
||||
|
||||
/// 100,000 strings, each too long for the small string optimization
|
||||
static std::vector<std::string> make_strings()
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(100000);
|
||||
for (std::size_t i = 0; i < 100000; ++i)
|
||||
{
|
||||
result.push_back("a string that does not fit in place: " + std::to_string(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void ToJsonStrings(benchmark::State& state, const bool move)
|
||||
{
|
||||
const std::vector<std::string> strings = make_strings();
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
state.PauseTiming();
|
||||
std::vector<std::string> input = strings;
|
||||
state.ResumeTiming();
|
||||
|
||||
auto* j = move ? new json(std::move(input)) : new json(input);
|
||||
|
||||
state.PauseTiming();
|
||||
delete j;
|
||||
state.ResumeTiming();
|
||||
}
|
||||
|
||||
state.SetItemsProcessed(state.iterations() * static_cast<std::int64_t>(strings.size()));
|
||||
}
|
||||
BENCHMARK_CAPTURE(ToJsonStrings, copy, false);
|
||||
BENCHMARK_CAPTURE(ToJsonStrings, move, true);
|
||||
|
||||
// conversion of a JSON array into standard containers
|
||||
template<typename Container>
|
||||
static void GetArray(benchmark::State& state)
|
||||
{
|
||||
json j = json::array();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
j.push_back(i);
|
||||
}
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
auto c = j.get<Container>();
|
||||
benchmark::DoNotOptimize(c);
|
||||
}
|
||||
state.SetItemsProcessed(state.iterations() * 1000);
|
||||
}
|
||||
|
||||
BENCHMARK_TEMPLATE(GetArray, std::vector<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::deque<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::set<int>);
|
||||
BENCHMARK_TEMPLATE(GetArray, std::array<int, 1000>);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
+10
-1
@@ -9,7 +9,16 @@ Each fuzzer driver (`tests/src/fuzzer-parse_*.cpp`) parses its input twice: once
|
||||
once with exceptions. Both calls must agree. Where parsing with exceptions fails, the call without exceptions must
|
||||
return a discarded value (or throw the same kind of non-parse error), and it must never throw a `parse_error`. Where
|
||||
parsing succeeds, both calls must return the same value. The drivers then serialize the value, parse the result back,
|
||||
and check that nothing was lost. The drivers check all of this with `assert`, so they refuse to build with `NDEBUG`.
|
||||
and check that nothing was lost.
|
||||
|
||||
Finally, each driver reads the same input through the other input adapters: a `std::istringstream`, a non-contiguous
|
||||
iterator range (`std::list<std::uint8_t>`), and, on POSIX systems, a `FILE*` from `fmemopen`. The pointer range takes the
|
||||
fast paths for contiguous input (bulk string scanning, multi-byte loads, lazy position tracking), while the other
|
||||
adapters read byte by byte. Every adapter must give the same value, or the same exception with the same id, message,
|
||||
and byte position. The JSON driver checks this for `parse` and `accept`, each with and without `ignore_comments`. The
|
||||
shared code is `check_input_adapters()` in `tests/src/fuzzer_common.hpp`.
|
||||
|
||||
The drivers check all of this with `assert`, so they refuse to build with `NDEBUG`.
|
||||
|
||||
## Corpus creation
|
||||
|
||||
|
||||
@@ -47,12 +47,30 @@ dumps is stable under exactly the same values that break operator==.
|
||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::from_bjdata for check_input_adapters()
|
||||
struct read_bjdata
|
||||
{
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::from_bjdata(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// value-stable comparison for the round-trip checks below; see the note
|
||||
// above on why this compares dump()s rather than the json values directly
|
||||
static bool is_value_stable(const json& lhs, const json& rhs)
|
||||
@@ -126,6 +144,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_bjdata());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -17,44 +17,33 @@ array data, it performs the following steps:
|
||||
- j2 = from_bon8(vec)
|
||||
- assert(to_bon8(j2) == vec)
|
||||
|
||||
It also checks that reading the data from a stream, which reads strings byte by
|
||||
byte, gives the same value or error as reading it from contiguous memory, which
|
||||
copies strings in bulk.
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <sstream>
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// the serialization of the value read from @a input, or the error message
|
||||
template<typename InputType>
|
||||
std::string read_bon8(InputType&& input)
|
||||
// json::from_bon8 for check_input_adapters()
|
||||
struct read_bon8
|
||||
{
|
||||
try
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
const auto vec = json::to_bon8(json::from_bon8(std::forward<InputType>(input)));
|
||||
return {vec.begin(), vec.end()};
|
||||
return json::from_bon8(std::forward<Args>(args)...);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
// contiguous and stream input must be read alike
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
assert(read_bon8(std::vector<uint8_t>(data, data + size)) == read_bon8(stream));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> const vec1(data, data + size);
|
||||
|
||||
// step 0: parse input without exceptions
|
||||
@@ -106,6 +95,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_bon8());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -17,12 +17,30 @@ array data, it performs the following steps:
|
||||
- j2 = from_bson(vec)
|
||||
- assert(to_bson(j2) == vec)
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::from_bson for check_input_adapters()
|
||||
struct read_bson
|
||||
{
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::from_bson(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -77,6 +95,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_bson());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -17,12 +17,30 @@ array data, it performs the following steps:
|
||||
- j2 = from_cbor(vec)
|
||||
- assert(to_cbor(j2) == vec)
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::from_cbor for check_input_adapters()
|
||||
struct read_cbor
|
||||
{
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::from_cbor(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -77,6 +95,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_cbor());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -17,6 +17,12 @@ array data, it performs the following steps:
|
||||
- j2 = parse(s1)
|
||||
- s2 = serialize(j2)
|
||||
- assert(s1 == s2)
|
||||
- for ignore_comments = false and true: assert(parse(data) and accept(data)
|
||||
give the same result for a stream, a non-contiguous iterator range, and a
|
||||
FILE* as for the pointer range)
|
||||
|
||||
The pointer range takes the bulk fast paths, the other input adapters read byte
|
||||
by byte; see check_input_adapters().
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
@@ -24,6 +30,32 @@ drivers.
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::parse and json::accept for check_input_adapters()
|
||||
struct parse_json
|
||||
{
|
||||
bool ignore_comments;
|
||||
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::parse(std::forward<Args>(args)..., nullptr, true, ignore_comments);
|
||||
}
|
||||
};
|
||||
|
||||
struct accept_json
|
||||
{
|
||||
bool ignore_comments;
|
||||
|
||||
template<typename... Args>
|
||||
bool operator()(Args&& ... args) const
|
||||
{
|
||||
return json::accept(std::forward<Args>(args)..., ignore_comments);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -76,6 +108,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
for (const bool ignore_comments :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
check_input_adapters(data, size, parse_json{ignore_comments});
|
||||
check_input_adapters(data, size, accept_json{ignore_comments});
|
||||
}
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -17,12 +17,30 @@ array data, it performs the following steps:
|
||||
- j2 = from_msgpack(vec)
|
||||
- assert(to_msgpack(j2) == vec)
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::from_msgpack for check_input_adapters()
|
||||
struct read_msgpack
|
||||
{
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::from_msgpack(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -77,6 +95,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_msgpack());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -26,12 +26,30 @@ array data, it performs the following steps:
|
||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
Finally, it reads the data from a stream, a non-contiguous iterator range, and
|
||||
a FILE*, which read byte by byte, and checks that each gives the same value or
|
||||
error (id, message, byte position) as the pointer range, which takes the bulk
|
||||
fast paths (see check_input_adapters()).
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include "fuzzer_common.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// json::from_ubjson for check_input_adapters()
|
||||
struct read_ubjson
|
||||
{
|
||||
template<typename... Args>
|
||||
json operator()(Args&& ... args) const
|
||||
{
|
||||
return json::from_ubjson(std::forward<Args>(args)...);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -96,6 +114,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
assert(parsed || noexcept_threw || j_noexcept.is_discarded());
|
||||
}
|
||||
|
||||
// step 3: the other input adapters must read the data alike
|
||||
check_input_adapters(data, size, read_ubjson());
|
||||
|
||||
// return 0 - non-zero return values are reserved for future use
|
||||
return 0;
|
||||
}
|
||||
@@ -11,6 +11,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert> // assert
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // FILE, fmemopen, fclose
|
||||
#include <list> // list
|
||||
#include <sstream> // istringstream
|
||||
#include <string> // string
|
||||
#include <utility> // forward
|
||||
#include <vector> // vector
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using nlohmann::json; // NOLINT(google-global-names-in-headers): shared by all fuzzer drivers
|
||||
@@ -49,3 +57,89 @@ json parse_without_exceptions(Parse parse, bool& threw)
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// the outcome of reading an input with exceptions: the serialization of the
|
||||
// value, or the id, message, and (for parse errors) byte of the exception
|
||||
struct read_outcome
|
||||
{
|
||||
std::string value{};
|
||||
int id = 0;
|
||||
std::string what{};
|
||||
std::size_t byte = 0;
|
||||
|
||||
bool operator==(const read_outcome& rhs) const
|
||||
{
|
||||
return value == rhs.value && id == rhs.id && what == rhs.what && byte == rhs.byte;
|
||||
}
|
||||
};
|
||||
|
||||
inline std::string outcome_value(const json& j)
|
||||
{
|
||||
return j.dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
|
||||
inline std::string outcome_value(bool b)
|
||||
{
|
||||
return b ? "true" : "false";
|
||||
}
|
||||
|
||||
template<typename Read, typename... Args>
|
||||
read_outcome read_with(const Read& parse, Args&& ... args)
|
||||
{
|
||||
read_outcome result;
|
||||
try
|
||||
{
|
||||
result.value = outcome_value(parse(std::forward<Args>(args)...));
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
result.id = e.id;
|
||||
result.what = e.what();
|
||||
result.byte = e.byte;
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
result.id = e.id;
|
||||
result.what = e.what();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The contiguous input (a pointer range) takes the bulk fast paths; the other
|
||||
// input adapters read byte by byte. Reads @a data through each of them and
|
||||
// checks that all give the same value, or the same exception with the same
|
||||
// id, message, and byte position.
|
||||
//
|
||||
// @a parse is called with either an iterator range or a single input (stream
|
||||
// or FILE*) and returns the parsed value (or the result of accept()).
|
||||
template<typename Read>
|
||||
void check_input_adapters(const std::uint8_t* data, std::size_t size, const Read& parse)
|
||||
{
|
||||
const read_outcome expected = read_with(parse, data, data + size);
|
||||
|
||||
// stream adapter
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(data), size));
|
||||
assert(read_with(parse, stream) == expected);
|
||||
}
|
||||
|
||||
// iterator adapter on a non-contiguous range
|
||||
{
|
||||
const std::list<std::uint8_t> list(data, data + size);
|
||||
assert(read_with(parse, list.begin(), list.end()) == expected);
|
||||
}
|
||||
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
// FILE* adapter; fmemopen may refuse an empty buffer
|
||||
{
|
||||
std::vector<std::uint8_t> buffer(data, data + size);
|
||||
std::FILE* file = buffer.empty() ? nullptr : fmemopen(buffer.data(), buffer.size(), "rb");
|
||||
if (file != nullptr)
|
||||
{
|
||||
const read_outcome actual = read_with(parse, file);
|
||||
std::fclose(file);
|
||||
assert(actual == expected);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -1149,3 +1150,157 @@ TEST_CASE("operator<=> of binary values with a different subtype does not depend
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("comparison of scalars (inline fast path)")
|
||||
{
|
||||
const json::number_integer_t neg = -1;
|
||||
const json::number_integer_t pos = 5;
|
||||
const json::number_unsigned_t big = (std::numeric_limits<json::number_unsigned_t>::max)();
|
||||
|
||||
SECTION("same type")
|
||||
{
|
||||
CHECK(json(pos) == json(pos));
|
||||
CHECK(json(pos) != json(neg));
|
||||
CHECK(json(neg) < json(pos));
|
||||
CHECK_FALSE(json(pos) < json(neg));
|
||||
CHECK_FALSE(json(pos) < json(pos));
|
||||
CHECK(json(pos) <= json(pos));
|
||||
|
||||
CHECK(json(1u) == json(1u));
|
||||
CHECK(json(1u) < json(2u));
|
||||
CHECK_FALSE(json(2u) < json(1u));
|
||||
|
||||
CHECK(json(1.5) == json(1.5));
|
||||
CHECK(json(1.5) < json(2.5));
|
||||
CHECK_FALSE(json(2.5) < json(1.5));
|
||||
CHECK(json(-0.0) == json(0.0));
|
||||
}
|
||||
|
||||
SECTION("mixed number types")
|
||||
{
|
||||
// negative signed vs unsigned must not wrap around
|
||||
CHECK(json(neg) < json(1u));
|
||||
CHECK(json(1u) > json(neg));
|
||||
CHECK(json(neg) != json(big));
|
||||
CHECK(json(neg) < json(big));
|
||||
CHECK_FALSE(json(big) < json(neg));
|
||||
|
||||
CHECK(json(pos) == json(5u));
|
||||
CHECK(json(5u) == json(pos));
|
||||
CHECK(json(pos) < json(6u));
|
||||
CHECK(json(pos) == json(5.0));
|
||||
CHECK(json(5.0) == json(pos));
|
||||
CHECK(json(5u) == json(5.0));
|
||||
CHECK(json(neg) < json(0.5));
|
||||
CHECK(json(0.5) < json(1u));
|
||||
}
|
||||
|
||||
SECTION("boolean and null")
|
||||
{
|
||||
CHECK(json(true) == json(true));
|
||||
CHECK(json(true) != json(false));
|
||||
CHECK(json(false) < json(true));
|
||||
CHECK_FALSE(json(true) < json(false));
|
||||
CHECK_FALSE(json(true) < json(true));
|
||||
|
||||
CHECK(json(nullptr) == json(nullptr));
|
||||
CHECK_FALSE(json(nullptr) < json(nullptr));
|
||||
CHECK(json(nullptr) <= json(nullptr));
|
||||
CHECK(json(nullptr) < json(false));
|
||||
CHECK(json(nullptr) != json(false));
|
||||
CHECK(json(nullptr) != json(0));
|
||||
}
|
||||
|
||||
SECTION("string vs number")
|
||||
{
|
||||
CHECK(json("5") != json(5));
|
||||
CHECK(json(5) != json("5"));
|
||||
CHECK(json(5) < json("5")); // number < string by type order
|
||||
CHECK_FALSE(json("5") < json(5));
|
||||
CHECK(json(1.5) < json(""));
|
||||
CHECK(json("a") == json("a"));
|
||||
CHECK(json("a") < json("b"));
|
||||
}
|
||||
|
||||
SECTION("NaN")
|
||||
{
|
||||
const json nan = std::numeric_limits<json::number_float_t>::quiet_NaN();
|
||||
const json one = 1.0;
|
||||
|
||||
CHECK_FALSE(nan == nan);
|
||||
CHECK(nan != nan);
|
||||
CHECK_FALSE(nan < nan);
|
||||
CHECK_FALSE(nan <= nan);
|
||||
CHECK_FALSE(nan >= nan);
|
||||
CHECK_FALSE(nan > nan);
|
||||
|
||||
CHECK_FALSE(nan == one);
|
||||
CHECK_FALSE(one == nan);
|
||||
CHECK_FALSE(nan < one);
|
||||
CHECK_FALSE(one < nan);
|
||||
CHECK_FALSE(nan <= one);
|
||||
CHECK_FALSE(one <= nan);
|
||||
CHECK_FALSE(nan >= one);
|
||||
CHECK_FALSE(one >= nan);
|
||||
|
||||
// NaN against other number types
|
||||
CHECK_FALSE(nan == json(1));
|
||||
CHECK_FALSE(json(1) == nan);
|
||||
CHECK_FALSE(nan < json(1u));
|
||||
CHECK_FALSE(json(1u) < nan);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((nan <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((nan <=> one) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> nan) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((one <=> json(2.0)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(2.0) <=> one) == std::partial_ordering::greater); // *NOPAD*
|
||||
CHECK((one <=> one) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(neg) <=> json(1u)) == std::partial_ordering::less); // *NOPAD*
|
||||
CHECK((json(nullptr) <=> json(nullptr)) == std::partial_ordering::equivalent); // *NOPAD*
|
||||
CHECK((json(true) <=> json(false)) == std::partial_ordering::greater); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("discarded")
|
||||
{
|
||||
const json d(json::value_t::discarded);
|
||||
|
||||
CHECK_FALSE(d == d);
|
||||
CHECK_FALSE(d < d);
|
||||
CHECK_FALSE(d == json(1));
|
||||
CHECK_FALSE(json(1) == d);
|
||||
CHECK_FALSE(d < json(1));
|
||||
CHECK_FALSE(json(1) < d);
|
||||
CHECK_FALSE(d == json(nullptr));
|
||||
CHECK_FALSE(json(nullptr) == d);
|
||||
|
||||
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||
CHECK((d <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((d <=> json(1)) == std::partial_ordering::unordered); // *NOPAD*
|
||||
CHECK((json(1) <=> d) == std::partial_ordering::unordered); // *NOPAD*
|
||||
#endif
|
||||
}
|
||||
|
||||
SECTION("scalar overloads")
|
||||
{
|
||||
CHECK(json(5) == 5);
|
||||
CHECK(5 == json(5));
|
||||
CHECK(json(5) < 6);
|
||||
CHECK(4 < json(5));
|
||||
CHECK(json(1.5) == 1.5);
|
||||
CHECK(json(true) == true);
|
||||
CHECK(json(5) != 6);
|
||||
CHECK(json(1u) == 1.0);
|
||||
}
|
||||
|
||||
SECTION("structured values still compare")
|
||||
{
|
||||
CHECK(json({1, 2}) == json({1, 2}));
|
||||
CHECK(json({1, 2}) < json({1, 3}));
|
||||
CHECK(json({{"a", 1}}) == json({{"a", 1}}));
|
||||
CHECK(json::binary({1, 2}) == json::binary({1, 2}));
|
||||
CHECK(json(1) < json::array());
|
||||
CHECK(json::object() < json::array());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
@@ -184,3 +187,193 @@ TEST_CASE("other constructors and destructor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("copy constructor and destructor across value types")
|
||||
{
|
||||
SECTION("copy of a scalar is independent of the source")
|
||||
{
|
||||
const json src_null = nullptr;
|
||||
const json src_bool = true;
|
||||
const json src_int = -7;
|
||||
const json src_uint = 7u;
|
||||
const json src_float = 2.5;
|
||||
|
||||
json c_null(src_null);
|
||||
json c_bool(src_bool);
|
||||
json c_int(src_int);
|
||||
json c_uint(src_uint);
|
||||
json c_float(src_float);
|
||||
|
||||
CHECK(c_null.is_null());
|
||||
CHECK(c_bool == true);
|
||||
CHECK(c_int == -7);
|
||||
CHECK(c_uint == 7u);
|
||||
CHECK(c_float == 2.5);
|
||||
|
||||
c_int = 8;
|
||||
c_float = 3.5;
|
||||
CHECK(src_int == -7);
|
||||
CHECK(src_float == 2.5);
|
||||
}
|
||||
|
||||
SECTION("copy of an owning value, then mutate the copy")
|
||||
{
|
||||
const json src_string = "hello";
|
||||
const json src_array = {1, 2, 3};
|
||||
const json src_object = {{"a", 1}, {"b", {{"c", 2}}}};
|
||||
const json src_binary = json::binary({1, 2, 3});
|
||||
|
||||
json c_string(src_string);
|
||||
json c_array(src_array);
|
||||
json c_object(src_object);
|
||||
json c_binary(src_binary);
|
||||
|
||||
CHECK(c_string == src_string);
|
||||
CHECK(c_array == src_array);
|
||||
CHECK(c_object == src_object);
|
||||
CHECK(c_binary == src_binary);
|
||||
|
||||
c_string.get_ref<json::string_t&>() += " world";
|
||||
c_array.push_back(4);
|
||||
c_object["b"]["c"] = 3;
|
||||
c_object["d"] = 4;
|
||||
c_binary.get_binary().push_back(4);
|
||||
|
||||
CHECK(src_string == "hello");
|
||||
CHECK(src_array.size() == 3);
|
||||
CHECK(src_object["b"]["c"] == 2);
|
||||
CHECK_FALSE(src_object.contains("d"));
|
||||
CHECK(src_binary.get_binary().size() == 3);
|
||||
|
||||
CHECK(c_string == "hello world");
|
||||
CHECK(c_array.size() == 4);
|
||||
CHECK(c_object["b"]["c"] == 3);
|
||||
CHECK(c_binary.get_binary().size() == 4);
|
||||
}
|
||||
|
||||
SECTION("copy keeps parent pointers valid (JSON_DIAGNOSTICS)")
|
||||
{
|
||||
const json src = {{"a", {1, 2}}, {"b", "x"}};
|
||||
json c(src);
|
||||
c["a"].push_back(3);
|
||||
CHECK(c["a"].size() == 3);
|
||||
CHECK(src["a"].size() == 2);
|
||||
}
|
||||
|
||||
SECTION("vector of mixed types: build, copy, and destroy")
|
||||
{
|
||||
{
|
||||
std::vector<json> v;
|
||||
for (int i = 0; i < 70; ++i)
|
||||
{
|
||||
switch (i % 9)
|
||||
{
|
||||
case 0:
|
||||
v.emplace_back(nullptr);
|
||||
break;
|
||||
case 1:
|
||||
v.emplace_back(i % 2 == 0);
|
||||
break;
|
||||
case 2:
|
||||
v.emplace_back(-i);
|
||||
break;
|
||||
case 3:
|
||||
v.emplace_back(static_cast<json::number_unsigned_t>(i));
|
||||
break;
|
||||
case 4:
|
||||
v.emplace_back(i * 0.5);
|
||||
break;
|
||||
case 5:
|
||||
v.emplace_back(std::string(static_cast<std::size_t>(i), 'x'));
|
||||
break;
|
||||
case 6:
|
||||
v.emplace_back(json::array({i, "s", nullptr}));
|
||||
break;
|
||||
case 7:
|
||||
v.emplace_back(json::object({{"k", i}}));
|
||||
break;
|
||||
default:
|
||||
v.emplace_back(json::binary({1, 2, 3}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
REQUIRE(v.size() == 70);
|
||||
|
||||
std::vector<json> copy(v);
|
||||
CHECK(copy == v);
|
||||
|
||||
// mutating the copy leaves the original alone
|
||||
copy[5] = "changed";
|
||||
copy[6].push_back(1);
|
||||
CHECK(copy != v);
|
||||
CHECK(v[5] == std::string(5, 'x'));
|
||||
CHECK(v[6].size() == 3);
|
||||
|
||||
v.erase(v.begin(), v.begin() + 10);
|
||||
CHECK(v.size() == 60);
|
||||
}
|
||||
|
||||
// destroying the discarded and default-constructed values
|
||||
std::vector<json> w(4);
|
||||
w.emplace_back(json::value_t::discarded);
|
||||
w.emplace_back(json::value_t::object);
|
||||
w.emplace_back(json::value_t::array);
|
||||
w.emplace_back(json::value_t::string);
|
||||
w.emplace_back(json::value_t::binary);
|
||||
CHECK(w.size() == 9);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("destructor of nested containers", Json, nlohmann::json, nlohmann::ordered_json)
|
||||
{
|
||||
SECTION("empty containers")
|
||||
{
|
||||
{
|
||||
Json j = Json::array();
|
||||
}
|
||||
{
|
||||
Json j = Json::object();
|
||||
}
|
||||
{
|
||||
Json j = Json::array({Json::array(), Json::object(), Json::array({Json::array()})});
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("leaves mixed with nested containers")
|
||||
{
|
||||
Json j = Json::object();
|
||||
j["leaf"] = 1;
|
||||
j["str"] = "text";
|
||||
j["empty_a"] = Json::array();
|
||||
j["empty_o"] = Json::object();
|
||||
j["arr"] = Json::array({1, "a", Json::array({1, 2}), Json::object({{"k", Json::array({Json::object()})}}), nullptr});
|
||||
j["obj"] = Json::object({{"x", Json::object({{"y", Json::array({1, Json::object({{"z", 1}})})}})}, {"w", 2}});
|
||||
Json copy = j;
|
||||
CHECK(copy == j);
|
||||
j = Json::array({std::move(copy), j, 3, Json::array()});
|
||||
}
|
||||
|
||||
SECTION("deep nesting")
|
||||
{
|
||||
for (int kind = 0; kind < 3; ++kind)
|
||||
{
|
||||
Json j = 1;
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
case 0:
|
||||
j = Json::array({std::move(j)});
|
||||
break;
|
||||
case 1:
|
||||
j = Json::object({{"k", std::move(j)}});
|
||||
break;
|
||||
default:
|
||||
// siblings next to the nested child
|
||||
j = (i % 2 == 0) ? Json::array({1, Json::array(), std::move(j)}) : Json::object({{"a", 1}, {"b", Json::object()}, {"c", std::move(j)}});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,11 @@
|
||||
#endif
|
||||
|
||||
#define JSON_DIAGNOSTICS 1
|
||||
#define JSON_TESTS_PRIVATE
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
@@ -631,3 +633,238 @@ TEST_CASE("Better diagnostics past the descent bound of update(), merge_patch()
|
||||
CHECK_THROWS_WITH_AS(p->at("y").at(0), expected_y.c_str(), json::type_error);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// check that every child's parent pointer points to the containing value
|
||||
template<typename BasicJsonType>
|
||||
bool parents_consistent(const BasicJsonType& j)
|
||||
{
|
||||
bool ok = true;
|
||||
if (j.is_array() || j.is_object())
|
||||
{
|
||||
for (const auto& child : j)
|
||||
{
|
||||
ok = ok && child.m_parent == &j && parents_consistent(child);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("Parent pointers stay consistent", J, json, ordered_json)
|
||||
{
|
||||
SECTION("parse, copy, move, assign, swap")
|
||||
{
|
||||
J j = J::parse(R"({"a":[1,{"b":[2,3]},[4]],"c":{"d":{"e":[]}}})");
|
||||
CHECK(parents_consistent(j));
|
||||
|
||||
J copy(j);
|
||||
CHECK(parents_consistent(copy));
|
||||
|
||||
J moved(std::move(copy));
|
||||
CHECK(parents_consistent(moved));
|
||||
|
||||
J assigned;
|
||||
assigned = j;
|
||||
CHECK(parents_consistent(assigned));
|
||||
|
||||
J other = {1, 2, {{"k", J::array({1, 2})}}};
|
||||
other.swap(assigned);
|
||||
CHECK(parents_consistent(other));
|
||||
CHECK(parents_consistent(assigned));
|
||||
}
|
||||
|
||||
SECTION("push_back growth")
|
||||
{
|
||||
J arr = J::array();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr.push_back(J::array({i}));
|
||||
}
|
||||
CHECK(parents_consistent(arr));
|
||||
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
obj.push_back({std::to_string(i), J::array({i})});
|
||||
}
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
|
||||
SECTION("emplace_back and emplace growth")
|
||||
{
|
||||
J arr = J::array();
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr.emplace_back(J::array({i}));
|
||||
obj.emplace(std::to_string(i), J::array({i}));
|
||||
}
|
||||
CHECK(parents_consistent(arr));
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
|
||||
SECTION("operator[] growth and at()")
|
||||
{
|
||||
J obj = J::object();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
obj[std::to_string(i)] = J::array({i});
|
||||
CHECK(obj[std::to_string(i / 2)].m_parent == &obj);
|
||||
}
|
||||
CHECK(parents_consistent(obj));
|
||||
|
||||
J arr = J::array();
|
||||
for (int i = 0; i < 500; ++i)
|
||||
{
|
||||
arr[static_cast<size_t>(i)] = J::object({{"x", i}});
|
||||
}
|
||||
for (size_t i = 0; i < 500; ++i)
|
||||
{
|
||||
CHECK(arr.at(i).m_parent == &arr);
|
||||
}
|
||||
CHECK(obj.at("3").m_parent == &obj);
|
||||
CHECK(parents_consistent(arr));
|
||||
CHECK(parents_consistent(obj));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("update() and merge_patch() keep parent pointers and diagnostic paths", J, json, ordered_json)
|
||||
{
|
||||
// enough keys to make the vector behind ordered_json reallocate several times
|
||||
const auto make_object = [](int first, int last)
|
||||
{
|
||||
J o = J::object();
|
||||
for (int i = first; i < last; ++i)
|
||||
{
|
||||
o[std::to_string(i)] = J::object({{"x", J::array({i})}});
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
// const access, so operator[] cannot repair the parent pointers
|
||||
const auto check_path = [](const J & j, const std::string & key)
|
||||
{
|
||||
const std::string expected = "[json.exception.type_error.304] (/" + key + "/x) cannot use at() with array";
|
||||
CHECK_THROWS_WITH_AS(j[key]["x"].at("y"), expected.c_str(), json::type_error);
|
||||
};
|
||||
|
||||
SECTION("update(const_reference)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
j.update(make_object(100, 400));
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "150");
|
||||
check_path(j, "399");
|
||||
}
|
||||
|
||||
SECTION("update(const_reference, merge_objects)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
j["50"]["y"] = 1;
|
||||
J other = make_object(100, 400);
|
||||
other["50"] = J::object({{"z", J::array({1})}});
|
||||
j.update(other, true);
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(j["50"].size() == 3);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "399");
|
||||
}
|
||||
|
||||
SECTION("update(first, last)")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
J other = make_object(100, 400);
|
||||
j.update(other.begin(), other.end());
|
||||
CHECK(j.size() == 400);
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "0");
|
||||
check_path(j, "399");
|
||||
|
||||
J k = make_object(0, 200);
|
||||
k.update(other.begin(), other.end(), true);
|
||||
CHECK(k.size() == 400);
|
||||
CHECK(parents_consistent(k));
|
||||
check_path(k, "399");
|
||||
}
|
||||
|
||||
SECTION("merge_patch()")
|
||||
{
|
||||
J j = make_object(0, 200);
|
||||
J patch = make_object(100, 400);
|
||||
patch["0"] = nullptr;
|
||||
j.merge_patch(patch);
|
||||
CHECK(j.size() == 399);
|
||||
CHECK_FALSE(j.contains("0"));
|
||||
CHECK(parents_consistent(j));
|
||||
check_path(j, "1");
|
||||
check_path(j, "399");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// an object type that, like a sorted flat map, moves its members when a key is
|
||||
// added even if its capacity does not change; it has a capacity() member, but
|
||||
// is neither node-based nor nlohmann::ordered_map, so the library must not
|
||||
// trust its capacity to tell whether the members moved
|
||||
template<class Key, class T, class IgnoredLess = std::less<Key>,
|
||||
class Allocator = std::allocator<std::pair<const Key, T>>>
|
||||
struct relocating_map : nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>
|
||||
{
|
||||
using base_type = nlohmann::ordered_map<Key, T, IgnoredLess, Allocator>;
|
||||
using typename base_type::iterator;
|
||||
using typename base_type::Container;
|
||||
using base_type::base_type;
|
||||
|
||||
template<typename... Args>
|
||||
std::pair<iterator, bool> emplace(Args&& ... args)
|
||||
{
|
||||
auto result = base_type::emplace(std::forward<Args>(args)...);
|
||||
const auto index = result.first - this->begin();
|
||||
if (result.second)
|
||||
{
|
||||
// move every member to a new buffer of the same capacity
|
||||
Container relocated;
|
||||
relocated.reserve(this->capacity());
|
||||
for (auto& member : *this)
|
||||
{
|
||||
relocated.emplace_back(member.first, std::move(member.second));
|
||||
}
|
||||
this->Container::swap(relocated);
|
||||
}
|
||||
return {this->begin() + index, result.second};
|
||||
}
|
||||
};
|
||||
|
||||
using relocating_json = nlohmann::basic_json<relocating_map>;
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Parent pointers stay consistent for object types not known to keep their members in place")
|
||||
{
|
||||
relocating_json obj = relocating_json::object();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
obj[std::to_string(i)] = relocating_json::array({i});
|
||||
obj.emplace(std::to_string(i + 1000), relocating_json::array({i}));
|
||||
}
|
||||
CHECK(parents_consistent(obj));
|
||||
|
||||
obj.erase("0");
|
||||
CHECK(parents_consistent(obj));
|
||||
|
||||
relocating_json other = relocating_json::object();
|
||||
for (int i = 0; i < 100; ++i)
|
||||
{
|
||||
other[std::to_string(i + 2000)] = relocating_json::array({i});
|
||||
}
|
||||
obj.update(other);
|
||||
CHECK(parents_consistent(obj));
|
||||
|
||||
obj["1"].push_back(42);
|
||||
CHECK_THROWS_WITH_AS(obj["1"].at(5), "[json.exception.out_of_range.401] (/1) array index 5 is out of range", relocating_json::out_of_range&);
|
||||
}
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#define JSON_TESTS_PRIVATE
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using nlohmann::json;
|
||||
|
||||
TEST_CASE("iterators 1")
|
||||
@@ -1628,3 +1630,108 @@ TEST_CASE("iterators 1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("reverse iteration and member access", Json, nlohmann::json, nlohmann::ordered_json)
|
||||
{
|
||||
SECTION("array: -- and rbegin/rend")
|
||||
{
|
||||
Json j = {1, 2, 3, 4};
|
||||
|
||||
auto it = j.end();
|
||||
std::vector<int> seen;
|
||||
while (it != j.begin())
|
||||
{
|
||||
--it;
|
||||
seen.push_back(it->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
|
||||
auto it2 = j.end();
|
||||
auto old = it2--;
|
||||
CHECK(old == j.end());
|
||||
CHECK(*it2 == 4);
|
||||
|
||||
seen.clear();
|
||||
for (auto r = j.rbegin(); r != j.rend(); ++r)
|
||||
{
|
||||
seen.push_back(r->template get<int>());
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
|
||||
seen.clear();
|
||||
for (auto r = j.crbegin(); r != j.crend(); ++r)
|
||||
{
|
||||
seen.push_back(*r);
|
||||
}
|
||||
CHECK(seen == std::vector<int> {4, 3, 2, 1});
|
||||
}
|
||||
|
||||
SECTION("object: -- and rbegin/rend")
|
||||
{
|
||||
Json j = {{"a", 1}, {"b", 2}, {"c", 3}};
|
||||
// order of keys depends on the object type; use the forward order as reference
|
||||
std::vector<std::string> fwd;
|
||||
for (auto it = j.begin(); it != j.end(); ++it)
|
||||
{
|
||||
fwd.push_back(it.key());
|
||||
}
|
||||
std::vector<std::string> rev(fwd.rbegin(), fwd.rend());
|
||||
|
||||
std::vector<std::string> seen;
|
||||
auto it = j.end();
|
||||
while (it != j.begin())
|
||||
{
|
||||
--it;
|
||||
seen.push_back(it.key());
|
||||
}
|
||||
CHECK(seen == rev);
|
||||
|
||||
auto it2 = j.end();
|
||||
auto old = it2--;
|
||||
CHECK(old == j.end());
|
||||
CHECK(it2.key() == rev.front());
|
||||
|
||||
seen.clear();
|
||||
for (auto r = j.rbegin(); r != j.rend(); ++r)
|
||||
{
|
||||
seen.push_back(r.key());
|
||||
}
|
||||
CHECK(seen == rev);
|
||||
}
|
||||
|
||||
SECTION("object: operator-> reads and writes the mapped value")
|
||||
{
|
||||
Json j = {{"a", 1}, {"s", "str"}};
|
||||
auto it = j.find("s");
|
||||
REQUIRE(it != j.end());
|
||||
CHECK(it->is_string());
|
||||
CHECK(it->size() == 1);
|
||||
*it = Json::array({1, 2});
|
||||
CHECK(it->is_array());
|
||||
it->push_back(3);
|
||||
CHECK(j["s"].size() == 3);
|
||||
|
||||
const Json& cj = j;
|
||||
auto cit = cj.find("a");
|
||||
REQUIRE(cit != cj.end());
|
||||
CHECK(cit->is_number_integer());
|
||||
CHECK(cit->template get<int>() == 1);
|
||||
}
|
||||
|
||||
SECTION("primitives: -- and ->")
|
||||
{
|
||||
Json j = 42;
|
||||
auto it = j.end();
|
||||
--it;
|
||||
CHECK(it == j.begin());
|
||||
CHECK(it->template get<int>() == 42);
|
||||
}
|
||||
|
||||
SECTION("comparing iterators of different containers throws")
|
||||
{
|
||||
Json a = {1, 2};
|
||||
Json b = {1, 2};
|
||||
CHECK_THROWS_WITH_AS(a.begin() == b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
|
||||
CHECK_THROWS_WITH_AS(a.begin() < b.begin(), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers", nlohmann::json::invalid_iterator);
|
||||
}
|
||||
}
|
||||
@@ -2066,3 +2066,40 @@ TEST_CASE("JSON patch - every operation on ordered_json")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_TEMPLATE("JSON patch - diff round trip on hand-made documents", Json, nlohmann::json, nlohmann::ordered_json)
|
||||
{
|
||||
const auto roundtrip = [](const Json & a, const Json & b)
|
||||
{
|
||||
CAPTURE(a.dump())
|
||||
CAPTURE(b.dump())
|
||||
const Json p = Json::diff(a, b);
|
||||
CHECK(a.patch(p) == b);
|
||||
CHECK(Json::diff(b, b).empty());
|
||||
};
|
||||
|
||||
SECTION("reordered keys")
|
||||
{
|
||||
const Json a = Json::parse(R"({"x":1,"y":2,"z":{"p":1,"q":2}})");
|
||||
const Json b = Json::parse(R"({"z":{"q":2,"p":1},"y":2,"x":1})");
|
||||
roundtrip(a, b);
|
||||
roundtrip(b, a);
|
||||
}
|
||||
|
||||
SECTION("nested add, remove and replace")
|
||||
{
|
||||
const Json a = Json::parse(R"({"a":[1,2,{"k":"v","gone":true}],"b":{"c":{"d":1}},"e":"s"})");
|
||||
const Json b = Json::parse(R"({"a":[1,3,{"k":"w","new":[]}],"b":{"c":{"d":[1,2]}},"f":null})");
|
||||
roundtrip(a, b);
|
||||
roundtrip(b, a);
|
||||
}
|
||||
|
||||
SECTION("arrays of different length and type changes")
|
||||
{
|
||||
roundtrip(Json::parse("[1,2,3,4,5]"), Json::parse("[1,3]"));
|
||||
roundtrip(Json::parse("[1]"), Json::parse("[1,[2],{\"a\":3}]"));
|
||||
roundtrip(Json::parse("[]"), Json::parse("{}"));
|
||||
roundtrip(Json::parse("{\"a\":[]}"), Json::parse("{\"a\":{}}"));
|
||||
roundtrip(Json(1), Json::parse("[1]"));
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,14 @@ using nlohmann::json;
|
||||
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
|
||||
#endif
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
TEST_CASE("JSON pointers")
|
||||
{
|
||||
@@ -1071,3 +1077,242 @@ TEST_CASE("flatten of structured values")
|
||||
R"({"/z":"s","/a~1b/~0":1,"/a~1b/k":null,"/a/0":1,"/a/1/c":null,"/a/1/b":null,"/a/2/0/x~0~1/0":true,"/a/2/0/x~0~1/1":null,"/a/2/0/w":2})");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("flatten of ordered_json")
|
||||
{
|
||||
using nlohmann::ordered_json;
|
||||
|
||||
SECTION("keeps the document order of the leaves")
|
||||
{
|
||||
const ordered_json j = ordered_json::parse(R"({
|
||||
"z": 1, "a": {"y": [true, null, {}], "b": "x", "": []},
|
||||
"m~n": 2.5, "o/p": {"k": {}}, "0": [[1], [2, 3]]
|
||||
})");
|
||||
|
||||
const ordered_json flat = j.flatten();
|
||||
std::vector<std::string> keys;
|
||||
for (const auto& el : flat.items())
|
||||
{
|
||||
keys.push_back(el.key());
|
||||
}
|
||||
const std::vector<std::string> expected =
|
||||
{
|
||||
"/z", "/a/y/0", "/a/y/1", "/a/y/2", "/a/b", "/a/", "/m~0n", "/o~1p/k", "/0/0/0", "/0/1/0", "/0/1/1"
|
||||
};
|
||||
CHECK(keys == expected);
|
||||
CHECK(flat.size() == expected.size());
|
||||
CHECK(flat["/m~0n"] == 2.5);
|
||||
CHECK(flat["/a/y/2"].is_null());
|
||||
}
|
||||
|
||||
SECTION("round-trips and agrees with json")
|
||||
{
|
||||
ordered_json j;
|
||||
for (int i = 0; i < 200; ++i)
|
||||
{
|
||||
j["k" + std::to_string(i)] = {{"v", i}, {"l", {i, i + 1, "s"}}};
|
||||
}
|
||||
const ordered_json flat = j.flatten();
|
||||
CHECK(flat.size() == 800);
|
||||
CHECK(flat.unflatten() == j);
|
||||
|
||||
// same entries as for a sorted object
|
||||
const nlohmann::json plain = nlohmann::json::parse(j.dump());
|
||||
CHECK(nlohmann::json::parse(flat.dump()) == plain.flatten());
|
||||
}
|
||||
|
||||
SECTION("primitive and empty root")
|
||||
{
|
||||
CHECK(ordered_json(1).flatten() == ordered_json({{"", 1}}));
|
||||
CHECK(ordered_json::array().flatten() == ordered_json({{"", nullptr}}));
|
||||
}
|
||||
}
|
||||
namespace
|
||||
{
|
||||
// reference implementation of the array index rules (RFC 6901, Sect. 4) based on strtoull
|
||||
enum class index_class
|
||||
{
|
||||
ok,
|
||||
leading_zero,
|
||||
not_a_number,
|
||||
unresolved,
|
||||
exceeds_size_type
|
||||
};
|
||||
|
||||
index_class classify_index(const std::string& s, std::size_t& idx)
|
||||
{
|
||||
if (s.size() > 1 && s[0] == '0')
|
||||
{
|
||||
return index_class::leading_zero;
|
||||
}
|
||||
if (!s.empty() && s != "-" && !(s[0] >= '0' && s[0] <= '9'))
|
||||
{
|
||||
return index_class::not_a_number;
|
||||
}
|
||||
char* end = nullptr;
|
||||
errno = 0;
|
||||
const unsigned long long res = std::strtoull(s.c_str(), &end, 10); // NOLINT(runtime/int)
|
||||
if (end == s.c_str() || errno == ERANGE || static_cast<std::size_t>(end - s.c_str()) != s.size())
|
||||
{
|
||||
return index_class::unresolved;
|
||||
}
|
||||
if (res >= static_cast<unsigned long long>((std::numeric_limits<std::size_t>::max)())) // NOLINT(runtime/int)
|
||||
{
|
||||
return index_class::exceeds_size_type;
|
||||
}
|
||||
idx = static_cast<std::size_t>(res);
|
||||
return index_class::ok;
|
||||
}
|
||||
|
||||
// the observable behavior of json_pointer::at() and contains() for an array index token
|
||||
void check_index_token(const std::string& token)
|
||||
{
|
||||
const json array = {10, 20, 30};
|
||||
const json::json_pointer ptr("/" + token);
|
||||
|
||||
std::size_t idx = 0;
|
||||
const auto cls = classify_index(token, idx);
|
||||
const bool exists = cls == index_class::ok && idx < array.size();
|
||||
|
||||
CAPTURE(token)
|
||||
CHECK(array.contains(ptr) == (exists && token != "-"));
|
||||
|
||||
if (token == "-")
|
||||
{
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cls)
|
||||
{
|
||||
case index_class::leading_zero:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||
break;
|
||||
case index_class::not_a_number:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::parse_error&);
|
||||
break;
|
||||
case index_class::unresolved:
|
||||
case index_class::exceeds_size_type:
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
break;
|
||||
case index_class::ok:
|
||||
default:
|
||||
if (exists)
|
||||
{
|
||||
CHECK(array.at(ptr) == array[idx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_THROWS_AS(array.at(ptr), json::out_of_range&);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("JSON pointer array index tokens match strtoull semantics")
|
||||
{
|
||||
SECTION("boundary values")
|
||||
{
|
||||
const std::string max_digits = std::to_string((std::numeric_limits<std::size_t>::max)());
|
||||
const std::vector<std::string> tokens =
|
||||
{
|
||||
"", "-", "0", "1", "2", "3", "00", "01", "007", "+1", "-1", " 1", "1 ", "1a", "a1", "0x1", "1e1", "1.0",
|
||||
"999999999", "1000000000", "99999999999999999", "999999999999999999", "1000000000000000000",
|
||||
"9999999999999999999", "18446744073709551614", max_digits, "18446744073709551616",
|
||||
"99999999999999999999999", "000000000000000000000"
|
||||
};
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
check_index_token(token);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("random tokens")
|
||||
{
|
||||
std::mt19937 gen(42);
|
||||
const std::string alphabet = "0123456789012345678901234567890123456789-+ ex";
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
std::string token(static_cast<std::size_t>(gen() % 23), '0');
|
||||
for (auto& c : token)
|
||||
{
|
||||
c = alphabet[gen() % alphabet.size()];
|
||||
}
|
||||
check_index_token(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON pointer operations on pointers with escaped tokens")
|
||||
{
|
||||
const std::vector<std::string> strings =
|
||||
{
|
||||
"", "/", "//", "/a", "/a/b", "/a~1b", "/~0", "/~01", "/~10", "/a~0~1/b/", "/0/1/2", "/ ", "/a/", "/~1/~0"
|
||||
};
|
||||
|
||||
for (const auto& str : strings)
|
||||
{
|
||||
CAPTURE(str)
|
||||
const json::json_pointer ptr(str);
|
||||
CHECK(ptr.to_string() == str);
|
||||
CHECK(ptr / "x" / "y" / std::size_t{3} == json::json_pointer(str + "/x/y/3"));
|
||||
CHECK(ptr.parent_pointer() == (str.empty() ? ptr : json::json_pointer(str.substr(0, str.rfind('/')))));
|
||||
|
||||
// comparison with a string, in place, and with every other pointer
|
||||
#if !defined(JSON_TEST_DEPRECATED_FUNCTIONS_DELETED)
|
||||
for (const auto& other : strings)
|
||||
{
|
||||
CAPTURE(other)
|
||||
CHECK((ptr == other) == (str == other));
|
||||
CHECK((other == ptr) == (str == other));
|
||||
CHECK((ptr != other) == (str != other));
|
||||
}
|
||||
CHECK_THROWS_AS(ptr == "x", json::parse_error&);
|
||||
CHECK_THROWS_AS(ptr == "/~", json::parse_error&);
|
||||
CHECK_THROWS_AS(ptr == "/b/~2", json::parse_error&);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("JSON pointer lookups")
|
||||
{
|
||||
json j = {{"a", {{"b", {1, 2, 3}}}}, {"", 7}, {"~/", {{"x", nullptr}}}};
|
||||
const json& cj = j;
|
||||
|
||||
CHECK(cj[json::json_pointer("/a/b/2")] == 3);
|
||||
CHECK(cj.at(json::json_pointer("/a/b/1")) == 2);
|
||||
CHECK(cj.at(json::json_pointer("/")) == 7);
|
||||
CHECK(cj.contains(json::json_pointer("/~0~1/x")));
|
||||
CHECK(!cj.contains(json::json_pointer("/a/c")));
|
||||
CHECK(cj.value(json::json_pointer("/a/b/0"), 5) == 1);
|
||||
CHECK(cj.value(json::json_pointer("/a/b/3"), 5) == 5);
|
||||
CHECK(cj.value(json::json_pointer("/a/zz"), 5) == 5);
|
||||
|
||||
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/c")), json::out_of_range&);
|
||||
CHECK_THROWS_AS(cj.at(json::json_pointer("/a/b/3")), json::out_of_range&);
|
||||
|
||||
// non-const access reuses existing entries and creates missing ones
|
||||
j[json::json_pointer("/a/b/1")] = 20;
|
||||
j[json::json_pointer("/n/m")] = 1;
|
||||
CHECK(j["a"]["b"] == json({1, 20, 3}));
|
||||
CHECK(j["n"]["m"] == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("JSON patch with moves and copies")
|
||||
{
|
||||
json doc = {{"a", {{"b", {1, 2, 3}}}}, {"c", {{"d", "x"}}}};
|
||||
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b","path":"/c/e"}])")) ==
|
||||
json::parse(R"({"a":{},"c":{"d":"x","e":[1,2,3]}})"));
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"copy","from":"/a/b","path":"/c/e"}])")) ==
|
||||
json::parse(R"({"a":{"b":[1,2,3]},"c":{"d":"x","e":[1,2,3]}})"));
|
||||
CHECK(doc.patch(json::parse(R"([{"op":"move","from":"/a/b/0","path":"/a/b/2"}])")) ==
|
||||
json::parse(R"({"a":{"b":[2,3,1]},"c":{"d":"x"}})"));
|
||||
|
||||
// a failing move leaves a copy-patched document untouched
|
||||
CHECK_THROWS_AS(doc.patch(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||
json inplace = doc;
|
||||
CHECK_THROWS_AS(inplace.patch_inplace(json::parse(R"([{"op":"move","from":"","path":""}])")), json::out_of_range&);
|
||||
CHECK(inplace == doc);
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// basic_json with the same type for number_integer_t and number_unsigned_t
|
||||
// (#2573): every integer is stored as number_integer
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
template<typename IntegerType>
|
||||
using same_types_json = nlohmann::json::with_integers_t<IntegerType, IntegerType>;
|
||||
|
||||
using json64 = same_types_json<std::int64_t>;
|
||||
using json32 = same_types_json<std::int32_t>;
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE_TEMPLATE("same type for number_integer_t and number_unsigned_t", Json, json64, json32)
|
||||
{
|
||||
using integer_t = typename Json::number_integer_t;
|
||||
using limits = std::numeric_limits<integer_t>;
|
||||
|
||||
SECTION("parsing stores integers as number_integer")
|
||||
{
|
||||
const Json j = Json::parse("[0, 1, -1, 2147483647, -2147483648]");
|
||||
for (const auto& element : j)
|
||||
{
|
||||
CHECK(element.is_number_integer());
|
||||
CHECK(!element.is_number_unsigned());
|
||||
CHECK(element.type() == nlohmann::detail::value_t::number_integer);
|
||||
}
|
||||
CHECK(j[1].template get<integer_t>() == 1);
|
||||
CHECK(j[2].template get<integer_t>() == -1);
|
||||
CHECK(j[3].template get<integer_t>() == 2147483647);
|
||||
CHECK(j[4].template get<std::int64_t>() == -2147483648LL);
|
||||
}
|
||||
|
||||
SECTION("integers above the maximum are parsed as floats")
|
||||
{
|
||||
// a positive literal is parsed as unsigned first; it must not wrap
|
||||
// around to a negative number_integer_t
|
||||
const auto above_max = std::to_string(static_cast<std::uint64_t>(limits::max()) + 1);
|
||||
const Json j1 = Json::parse(above_max);
|
||||
CHECK(j1.is_number_float());
|
||||
CHECK(j1.template get<double>() == static_cast<double>(static_cast<std::uint64_t>(limits::max()) + 1));
|
||||
|
||||
const Json j2 = Json::parse("18446744073709551615");
|
||||
CHECK(j2.is_number_float());
|
||||
CHECK(j2.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
const Json j3 = Json::parse(std::to_string(limits::max()));
|
||||
CHECK(j3.is_number_integer());
|
||||
CHECK(j3.template get<integer_t>() == limits::max());
|
||||
}
|
||||
|
||||
SECTION("serialization of the extremes")
|
||||
{
|
||||
CHECK(Json(limits::max()).dump() == std::to_string(limits::max()));
|
||||
// the absolute value of the minimum does not fit into number_unsigned_t
|
||||
CHECK(Json(limits::min()).dump() == std::to_string(limits::min()));
|
||||
CHECK(Json::parse(std::to_string(limits::min())) == Json(limits::min()));
|
||||
CHECK(Json(0).dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("construction from unsigned types")
|
||||
{
|
||||
const Json j1 = 5u;
|
||||
CHECK(j1.is_number_integer());
|
||||
CHECK(!j1.is_number_unsigned());
|
||||
CHECK(j1 == 5);
|
||||
|
||||
const Json j2 = static_cast<std::uint8_t>(200);
|
||||
CHECK(j2.is_number_integer());
|
||||
CHECK(j2.template get<integer_t>() == 200);
|
||||
|
||||
const Json j3 = std::vector<unsigned> {1, 2, 3};
|
||||
CHECK(j3.dump() == "[1,2,3]");
|
||||
CHECK(j3[0].is_number_integer());
|
||||
|
||||
const Json j4 = true;
|
||||
CHECK(j4.is_boolean());
|
||||
}
|
||||
|
||||
SECTION("value_t::number_unsigned creates a number_integer")
|
||||
{
|
||||
const Json j(nlohmann::detail::value_t::number_unsigned);
|
||||
CHECK(j.is_number_integer());
|
||||
CHECK(!j.is_number_unsigned());
|
||||
CHECK(j == 0);
|
||||
CHECK(j.dump() == "0");
|
||||
}
|
||||
|
||||
SECTION("conversions")
|
||||
{
|
||||
Json j = 42;
|
||||
CHECK(j.template get<unsigned>() == 42u);
|
||||
CHECK(j.template get<std::uint64_t>() == 42u);
|
||||
CHECK(j.template get<int>() == 42);
|
||||
CHECK(j.template get<double>() == 42.0);
|
||||
CHECK(j.template get_ptr<integer_t*>() != nullptr);
|
||||
CHECK(*j.template get_ptr<integer_t*>() == 42);
|
||||
CHECK(j.template get_ref<integer_t&>() == 42);
|
||||
j.template get_ref<integer_t&>() = 43;
|
||||
CHECK(j == 43);
|
||||
|
||||
integer_t i = 0;
|
||||
j.get_to(i);
|
||||
CHECK(i == 43);
|
||||
|
||||
const Json object = {{"a", 1}};
|
||||
CHECK(object.value("a", 0u) == 1u);
|
||||
CHECK(object.value("b", 7u) == 7u);
|
||||
}
|
||||
|
||||
SECTION("conversion from a basic_json with distinct types")
|
||||
{
|
||||
const nlohmann::json j = {1, 2u, -3, 4.5};
|
||||
CHECK(j[1].is_number_unsigned());
|
||||
const Json converted = j;
|
||||
CHECK(converted.dump() == "[1,2,-3,4.5]");
|
||||
CHECK(converted[1].is_number_integer());
|
||||
CHECK(!converted[1].is_number_unsigned());
|
||||
|
||||
const nlohmann::json back = converted;
|
||||
CHECK(back == j);
|
||||
}
|
||||
|
||||
SECTION("comparison and hashing")
|
||||
{
|
||||
const Json j1 = 1;
|
||||
const Json j2 = 2u;
|
||||
CHECK(j1 < j2);
|
||||
CHECK(j1 != j2);
|
||||
CHECK(j2 == 2);
|
||||
CHECK(j2 == 2u);
|
||||
CHECK(j1 < 1.5);
|
||||
CHECK(Json(-1) < Json(0u));
|
||||
CHECK(std::hash<Json> {}(Json(5u)) == std::hash<Json> {}(Json(5)));
|
||||
}
|
||||
|
||||
SECTION("JSON Patch, diff, and flatten")
|
||||
{
|
||||
const Json source = Json::parse(R"({"a": [1, 2, 3], "b": 4})");
|
||||
const Json target = Json::parse(R"({"a": [1, 5], "c": 6})");
|
||||
CHECK(source.patch(Json::diff(source, target)) == target);
|
||||
CHECK(source.flatten().unflatten() == source);
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
const Json j = {{"min", limits::min()}, {"max", limits::max()}, {"zero", 0}, {"small", 100}, {"negative", -100}, {"float", 1.5}};
|
||||
|
||||
CHECK(Json::from_cbor(Json::to_cbor(j)) == j);
|
||||
CHECK(Json::from_msgpack(Json::to_msgpack(j)) == j);
|
||||
CHECK(Json::from_ubjson(Json::to_ubjson(j)) == j);
|
||||
CHECK(Json::from_bjdata(Json::to_bjdata(j)) == j);
|
||||
CHECK(Json::from_bson(Json::to_bson(j)) == j);
|
||||
CHECK(Json::from_bon8(Json::to_bon8(j)) == j);
|
||||
|
||||
for (const auto& element : Json::from_cbor(Json::to_cbor(j)))
|
||||
{
|
||||
CHECK(!element.is_number_unsigned());
|
||||
}
|
||||
|
||||
// CBOR unsigned integer 2^64-1 does not fit and is read as float
|
||||
const std::vector<std::uint8_t> cbor_max = {0x1B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
const Json from_cbor = Json::from_cbor(cbor_max);
|
||||
CHECK(from_cbor.is_number_float());
|
||||
CHECK(from_cbor.template get<double>() == 18446744073709551615.0);
|
||||
|
||||
// MessagePack uint 8 is read as number_integer
|
||||
const std::vector<std::uint8_t> msgpack_uint8 = {0xCC, 0xC8};
|
||||
const Json from_msgpack = Json::from_msgpack(msgpack_uint8);
|
||||
CHECK(from_msgpack.is_number_integer());
|
||||
CHECK(from_msgpack == 200);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("same type for number_integer_t and number_unsigned_t: type traits")
|
||||
{
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<json64>, nlohmann::detail::same_as_number_integer_type>::value);
|
||||
CHECK(std::is_same<nlohmann::detail::number_unsigned_overload_t<nlohmann::json>, std::uint64_t>::value);
|
||||
CHECK(nlohmann::detail::is_compatible_number_integer_type<json64, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<nlohmann::json, unsigned>::value);
|
||||
CHECK(!nlohmann::detail::is_compatible_number_integer_type<json64, bool>::value);
|
||||
}
|
||||
@@ -20,8 +20,11 @@
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "test_utils.hpp"
|
||||
|
||||
@@ -923,66 +926,68 @@ TEST_CASE("serialization boundary values for the string buffer")
|
||||
// mid-string once fewer than 13 bytes remain (`string_buffer.size() - bytes
|
||||
// < 13`), 13 being one more than the most a single code point can ever
|
||||
// write at once (a surrogate pair: two back-to-back "\uXXXX" escapes, 12
|
||||
// bytes). Every write into string_buffer that this check protects happens
|
||||
// in steps of 2 (a simple "\\x" escape) or 6 (one "\uXXXX" unit), so
|
||||
// `bytes` only ever takes even values at the point the check runs - the
|
||||
// tightest values actually reachable are therefore 498 (512 - 498 == 14,
|
||||
// one simple escape away from the threshold) and 500 (512 - 500 == 12,
|
||||
// where the flush fires immediately and resets bytes to 0).
|
||||
// bytes). Characters below 0x60 (control characters, '"' and '\\') are
|
||||
// escaped straight into the write buffer and never reach string_buffer, so
|
||||
// under ensure_ascii it fills in steps of 6 (one "\uXXXX" unit for a
|
||||
// character below U+10000) or 12 (a surrogate pair). With 2-byte characters
|
||||
// such as U+00E4, `bytes` takes multiples of 6 at the point the check runs;
|
||||
// the tightest values reachable are therefore 498 (512 - 498 == 14, after
|
||||
// 83 characters) and 504 (512 - 504 == 8, after 84 characters, where the
|
||||
// flush fires immediately and resets bytes to 0).
|
||||
|
||||
SECTION("a run of 2-byte escapes lands bytes on, and one step past, the flush threshold")
|
||||
SECTION("a run of 6-byte escapes lands bytes on, and one step past, the flush threshold")
|
||||
{
|
||||
for (const int count :
|
||||
{
|
||||
249, 250, 251
|
||||
83, 84, 85
|
||||
})
|
||||
{
|
||||
CAPTURE(count)
|
||||
const json j = std::string(static_cast<std::size_t>(count), '\n');
|
||||
std::string umlauts;
|
||||
std::string expected = "\"";
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
expected += "\\n";
|
||||
umlauts += "\xC3\xA4";
|
||||
expected += "\\u00e4";
|
||||
}
|
||||
expected += '"';
|
||||
CHECK(j.dump() == expected);
|
||||
CHECK(json(umlauts).dump(-1, ' ', true) == expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("an ASCII prefix leaves the tightest reachable margin before a 12-byte surrogate pair")
|
||||
SECTION("a prefix leaves the tightest reachable margin before a 12-byte surrogate pair")
|
||||
{
|
||||
// U+1F600 (the "\xF0\x9F\x98\x80" UTF-8 bytes) is dumped under
|
||||
// ensure_ascii as the 12-byte surrogate pair "\ud83d\ude00"; that
|
||||
// write happens in a single step with no intermediate flush check, so
|
||||
// it is the write most exposed by an off-by-one in the "< 13" guard.
|
||||
// A prefix of 249 newlines leaves exactly 14 bytes of headroom
|
||||
// A prefix of 83 U+00E4 characters leaves exactly 14 bytes of headroom
|
||||
// (512 - 498), the smallest margin the guard ever actually allows
|
||||
// into a new code point; 250 newlines instead trigger the guard's own
|
||||
// into a new code point; 84 characters instead trigger the guard's own
|
||||
// flush first, so the emoji starts from a freshly emptied (512-byte)
|
||||
// buffer, and 251 repeats that with one more escape already past the
|
||||
// buffer, and 85 repeats that with one more escape already past the
|
||||
// reset. Together they cover the margin the guard allows landing on,
|
||||
// one step before, and one step after - all must still produce the
|
||||
// identical, correct escapes.
|
||||
for (const int prefix_count :
|
||||
{
|
||||
249, 250, 251
|
||||
83, 84, 85
|
||||
})
|
||||
{
|
||||
CAPTURE(prefix_count)
|
||||
const std::string prefix(static_cast<std::size_t>(prefix_count), '\n');
|
||||
const std::string emoji = "\xF0\x9F\x98\x80";
|
||||
const json j = prefix + emoji;
|
||||
|
||||
std::string prefix;
|
||||
std::string expected_prefix;
|
||||
for (int i = 0; i < prefix_count; ++i)
|
||||
{
|
||||
expected_prefix += "\\n";
|
||||
prefix += "\xC3\xA4";
|
||||
expected_prefix += "\\u00e4";
|
||||
}
|
||||
const std::string emoji = "\xF0\x9F\x98\x80";
|
||||
const json j = prefix + emoji;
|
||||
|
||||
// newline escaping does not depend on ensure_ascii: only the
|
||||
// emoji differs (raw UTF-8 bytes vs. a \u-escaped surrogate pair)
|
||||
// without ensure_ascii, the whole string is copied unchanged
|
||||
std::string expected_raw = "\"";
|
||||
expected_raw += expected_prefix;
|
||||
expected_raw += prefix;
|
||||
expected_raw += emoji;
|
||||
expected_raw += '"';
|
||||
std::string expected_ascii = "\"";
|
||||
@@ -1027,3 +1032,524 @@ TEST_CASE("serialization boundary values for the string buffer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// appends the escaped form of the ASCII character c to out; written
|
||||
// independently of the table in serializer::ascii_escapes()
|
||||
void append_escaped_ascii(std::string& out, const char c, const bool ensure_ascii)
|
||||
{
|
||||
const auto byte = static_cast<unsigned char>(c);
|
||||
switch (byte)
|
||||
{
|
||||
case '\b':
|
||||
out += "\\b";
|
||||
break;
|
||||
case '\t':
|
||||
out += "\\t";
|
||||
break;
|
||||
case '\n':
|
||||
out += "\\n";
|
||||
break;
|
||||
case '\f':
|
||||
out += "\\f";
|
||||
break;
|
||||
case '\r':
|
||||
out += "\\r";
|
||||
break;
|
||||
case '"':
|
||||
out += "\\\"";
|
||||
break;
|
||||
case '\\':
|
||||
out += "\\\\";
|
||||
break;
|
||||
default:
|
||||
if (byte < 0x20 || (ensure_ascii && byte == 0x7F))
|
||||
{
|
||||
// byte < 0x80, so the first two hex digits are 0
|
||||
const char* const hex = "0123456789abcdef";
|
||||
out += "\\u00";
|
||||
out += hex[byte >> 4U];
|
||||
out += hex[byte & 0x0FU];
|
||||
}
|
||||
else
|
||||
{
|
||||
out += c;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// reference escaper for ASCII strings
|
||||
std::string reference_escape_ascii(const std::string& s, const bool ensure_ascii)
|
||||
{
|
||||
std::string result = "\"";
|
||||
for (const char c : s)
|
||||
{
|
||||
append_escaped_ascii(result, c, ensure_ascii);
|
||||
}
|
||||
result += '"';
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("characters below 0x60 are escaped from a table straight into the write buffer")
|
||||
{
|
||||
SECTION("every ASCII character at every position")
|
||||
{
|
||||
// lengths up to 17 cover the 8-byte strides of the bulk scanner on
|
||||
// both sides of the character, so the scanner stops at it from every
|
||||
// offset
|
||||
for (std::size_t length = 1; length <= 17; ++length)
|
||||
{
|
||||
for (std::size_t pos = 0; pos < length; ++pos)
|
||||
{
|
||||
for (unsigned int c = 0; c < 0x80; ++c)
|
||||
{
|
||||
std::string s(length, 'a');
|
||||
s[pos] = static_cast<char>(c);
|
||||
CAPTURE(length)
|
||||
CAPTURE(pos)
|
||||
CAPTURE(c)
|
||||
const json j = s;
|
||||
CHECK(j.dump(-1, ' ', false) == reference_escape_ascii(s, false));
|
||||
CHECK(j.dump(-1, ' ', true) == reference_escape_ascii(s, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("runs of escaped characters")
|
||||
{
|
||||
std::string s;
|
||||
for (unsigned int c = 0; c < 0x20; ++c)
|
||||
{
|
||||
s += static_cast<char>(c);
|
||||
}
|
||||
s += R"("\"\)";
|
||||
CHECK(json(s).dump() == reference_escape_ascii(s, false));
|
||||
CHECK(json(s).dump(-1, ' ', true) == reference_escape_ascii(s, true));
|
||||
}
|
||||
|
||||
SECTION("escapes straddle the end of the write buffer")
|
||||
{
|
||||
// the 8-byte store of a table entry needs room for all 8 bytes, even
|
||||
// when the escape itself is shorter; a prefix of k plain characters
|
||||
// shifts the 6-byte escapes across every offset of the 1024-byte
|
||||
// write buffer
|
||||
for (std::size_t k = 0; k < 8; ++k)
|
||||
{
|
||||
CAPTURE(k)
|
||||
const std::string s = std::string(k, 'a') + std::string(400, '\x01');
|
||||
CHECK(json(s).dump() == reference_escape_ascii(s, false));
|
||||
CHECK(json::array({s, s}).dump() == "[" + reference_escape_ascii(s, false) + "," + reference_escape_ascii(s, false) + "]");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("pending \\u escapes of non-ASCII characters are written first")
|
||||
{
|
||||
// under ensure_ascii, U+00E4 is \u-escaped into the string buffer;
|
||||
// a following escape from the table must not overtake it
|
||||
const json j = "\xC3\xA4\n\xC3\xA4\"\xF0\x9F\x98\x80\\";
|
||||
CHECK(j.dump(-1, ' ', true) == "\"\\u00e4\\n\\u00e4\\\"\\ud83d\\ude00\\\\\"");
|
||||
CHECK(j.dump(-1, ' ', false) == "\"\xC3\xA4\\n\xC3\xA4\\\"\xF0\x9F\x98\x80\\\\\"");
|
||||
}
|
||||
|
||||
SECTION("escapes after invalid UTF-8 for every error handler")
|
||||
{
|
||||
const json j = "a\xFF\n\xC3\"";
|
||||
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xFF", json::type_error&);
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"a\\n\\\"\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::ignore) == "\"a\\n\\\"\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"a\xEF\xBF\xBD\\n\xEF\xBF\xBD\\\"\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"a\\ufffd\\n\\ufffd\\\"\"");
|
||||
CHECK(j.dump(-1, ' ', false, json::error_handler_t::keep) == "\"a\xFF\\n\xC3\\\"\"");
|
||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::keep) == "\"a\xFF\\n\xC3\\\"\"");
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// expected result of dumping a string: the output, or (with
|
||||
// error_handler_t::strict) the message of the type_error.316 exception
|
||||
struct expected_dump
|
||||
{
|
||||
bool throws = false;
|
||||
std::string text{}; // NOLINT(readability-redundant-member-init)
|
||||
};
|
||||
|
||||
std::string upper_hex_byte(const unsigned char byte)
|
||||
{
|
||||
const char* const hex = "0123456789ABCDEF";
|
||||
return std::string(1, hex[byte >> 4U]) + hex[byte & 0x0FU];
|
||||
}
|
||||
|
||||
void append_u_escape(std::string& out, const std::uint32_t code_unit)
|
||||
{
|
||||
const char* const hex = "0123456789abcdef";
|
||||
out += "\\u";
|
||||
for (int shift = 12; shift >= 0; shift -= 4)
|
||||
{
|
||||
out += hex[(code_unit >> static_cast<unsigned int>(shift)) & 0x0FU];
|
||||
}
|
||||
}
|
||||
|
||||
void append_replacement_character(std::string& out, const bool ensure_ascii)
|
||||
{
|
||||
out += ensure_ascii ? "\\ufffd" : "\xEF\xBF\xBD";
|
||||
}
|
||||
|
||||
// reference serializer for arbitrary byte strings, written from the documented
|
||||
// behavior of dump() and independently of serializer::dump_escaped(): UTF-8 is
|
||||
// decoded strictly (well-formed sequences as in Table 3-7 of the Unicode
|
||||
// Standard), and each ill-formed sequence is handled as the error handler says
|
||||
expected_dump reference_dump_string(const std::string& s, const bool ensure_ascii, const json::error_handler_t error_handler)
|
||||
{
|
||||
expected_dump result;
|
||||
std::string& out = result.text;
|
||||
out = "\"";
|
||||
|
||||
// handle the ill-formed bytes s[begin, end), of which s[index] is the byte
|
||||
// that could not be decoded; returns false if an exception is expected
|
||||
const auto ill_formed = [&](const std::size_t begin, const std::size_t end, const std::size_t index)
|
||||
{
|
||||
switch (error_handler)
|
||||
{
|
||||
case json::error_handler_t::strict:
|
||||
result.throws = true;
|
||||
out = "[json.exception.type_error.316] invalid UTF-8 byte at index " + std::to_string(index) + ": 0x" + upper_hex_byte(static_cast<unsigned char>(s[index]));
|
||||
return false;
|
||||
case json::error_handler_t::ignore:
|
||||
break;
|
||||
case json::error_handler_t::replace:
|
||||
append_replacement_character(out, ensure_ascii);
|
||||
break;
|
||||
case json::error_handler_t::keep:
|
||||
default:
|
||||
// kept as is, even with ensure_ascii
|
||||
out.append(s, begin, end - begin);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
std::size_t i = 0;
|
||||
while (i < s.size())
|
||||
{
|
||||
const auto lead = static_cast<unsigned char>(s[i]);
|
||||
if (lead < 0x80)
|
||||
{
|
||||
append_escaped_ascii(out, s[i], ensure_ascii);
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// number of bytes of the sequence and the range of its second byte
|
||||
std::size_t length = 0;
|
||||
std::uint32_t code_point = 0;
|
||||
unsigned char lower = 0x80;
|
||||
unsigned char upper = 0xBF;
|
||||
if (lead >= 0xC2 && lead <= 0xDF)
|
||||
{
|
||||
length = 2;
|
||||
code_point = lead & 0x1FU;
|
||||
}
|
||||
else if (lead >= 0xE0 && lead <= 0xEF)
|
||||
{
|
||||
length = 3;
|
||||
code_point = lead & 0x0FU;
|
||||
lower = (lead == 0xE0) ? 0xA0 : 0x80; // no overlong encodings
|
||||
upper = (lead == 0xED) ? 0x9F : 0xBF; // no surrogates
|
||||
}
|
||||
else if (lead >= 0xF0 && lead <= 0xF4)
|
||||
{
|
||||
length = 4;
|
||||
code_point = lead & 0x07U;
|
||||
lower = (lead == 0xF0) ? 0x90 : 0x80; // no overlong encodings
|
||||
upper = (lead == 0xF4) ? 0x8F : 0xBF; // nothing above U+10FFFF
|
||||
}
|
||||
else
|
||||
{
|
||||
// a byte that cannot start a sequence is consumed
|
||||
if (!ill_formed(i, i + 1, i))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::size_t j = i + 1;
|
||||
for (; j < i + length; ++j)
|
||||
{
|
||||
if (j == s.size())
|
||||
{
|
||||
// the string ends in the middle of the sequence
|
||||
switch (error_handler)
|
||||
{
|
||||
case json::error_handler_t::strict:
|
||||
result.throws = true;
|
||||
out = "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0x" + upper_hex_byte(static_cast<unsigned char>(s.back()));
|
||||
return result;
|
||||
case json::error_handler_t::ignore:
|
||||
break;
|
||||
case json::error_handler_t::replace:
|
||||
append_replacement_character(out, ensure_ascii);
|
||||
break;
|
||||
case json::error_handler_t::keep:
|
||||
default:
|
||||
out.append(s, i, s.size() - i);
|
||||
break;
|
||||
}
|
||||
out += '"';
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto byte = static_cast<unsigned char>(s[j]);
|
||||
if (byte < lower || byte > upper)
|
||||
{
|
||||
break;
|
||||
}
|
||||
code_point = (code_point << 6U) | (byte & 0x3FU);
|
||||
lower = 0x80;
|
||||
upper = 0xBF;
|
||||
}
|
||||
|
||||
if (j < i + length)
|
||||
{
|
||||
// the sequence was abandoned at s[j]; that byte is read again as
|
||||
// the start of a new character
|
||||
if (!ill_formed(i, j, j))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
i = j;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ensure_ascii)
|
||||
{
|
||||
out.append(s, i, length);
|
||||
}
|
||||
else if (code_point <= 0xFFFF)
|
||||
{
|
||||
append_u_escape(out, code_point);
|
||||
}
|
||||
else
|
||||
{
|
||||
append_u_escape(out, 0xD800U + ((code_point - 0x10000U) >> 10U));
|
||||
append_u_escape(out, 0xDC00U + ((code_point - 0x10000U) & 0x3FFU));
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
|
||||
out += '"';
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string hex_dump(const std::string& s)
|
||||
{
|
||||
std::string result;
|
||||
for (const char c : s)
|
||||
{
|
||||
result += upper_hex_byte(static_cast<unsigned char>(c));
|
||||
result += ' ';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// compares dump() of s with the reference for every error handler, with
|
||||
// ensure_ascii on and off; CHECKs only on a mismatch to keep the sweep fast;
|
||||
// error_handler_t::strict can be skipped where the position of the string
|
||||
// buffer does not matter, as throwing is slow on some platforms
|
||||
void check_dump_against_reference(const std::string& s, const bool with_strict = true)
|
||||
{
|
||||
const json j = s;
|
||||
const std::array<json::error_handler_t, 4> error_handlers =
|
||||
{
|
||||
{
|
||||
json::error_handler_t::strict, json::error_handler_t::ignore,
|
||||
json::error_handler_t::replace, json::error_handler_t::keep
|
||||
}
|
||||
};
|
||||
for (const auto error_handler : error_handlers)
|
||||
{
|
||||
if (!with_strict && error_handler == json::error_handler_t::strict)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (const bool ensure_ascii :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
const expected_dump expected = reference_dump_string(s, ensure_ascii, error_handler);
|
||||
expected_dump actual;
|
||||
int id = 0;
|
||||
try
|
||||
{
|
||||
actual.text = j.dump(-1, ' ', ensure_ascii, error_handler);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
actual.throws = true;
|
||||
actual.text = e.what();
|
||||
id = e.id;
|
||||
}
|
||||
if (actual.throws != expected.throws || actual.text != expected.text || (actual.throws && id != 316))
|
||||
{
|
||||
const std::string input = hex_dump(s);
|
||||
CAPTURE(input)
|
||||
CAPTURE(ensure_ascii)
|
||||
CAPTURE(static_cast<int>(error_handler))
|
||||
CHECK(actual.throws == expected.throws);
|
||||
CHECK(actual.text == expected.text);
|
||||
CHECK((!actual.throws || id == 316));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("invalid UTF-8 at every position is handled like the reference")
|
||||
{
|
||||
// ill-formed byte sequences; those that end in a truncated sequence are
|
||||
// also cut short by the filler that follows, or by the end of the string
|
||||
const std::vector<std::string> patterns =
|
||||
{
|
||||
// stray continuation bytes
|
||||
"\x80", "\xBF",
|
||||
// bytes that cannot start a sequence
|
||||
"\xC0", "\xC1", "\xF5", "\xF6", "\xF7", "\xF8", "\xF9", "\xFA", "\xFB", "\xFC", "\xFD", "\xFE", "\xFF",
|
||||
// overlong encodings
|
||||
"\xC0\xAF", "\xE0\x80\x80", "\xF0\x80\x80\x80",
|
||||
// UTF-16 surrogates
|
||||
"\xED\xA0\x80", "\xED\xBF\xBF",
|
||||
// a code point above U+10FFFF
|
||||
"\xF4\x90\x80\x80",
|
||||
// sequences cut short by ASCII, a control character, a quote, a
|
||||
// backslash, DEL, or the lead byte of a well-formed character
|
||||
"\xC3" "a", "\xE2\x82" "\n", "\xE2\x82\"", "\xF0\x9F\x98\\", "\xC3\x7F", "\xC3\xC3\xA4", "\xE2\xF0\x9F\x98\x80",
|
||||
// truncated sequences
|
||||
"\xC3", "\xE2\x82", "\xF0\x9F\x98"
|
||||
};
|
||||
|
||||
// well-formed characters of 1, 2, 3, and 4 bytes
|
||||
const std::vector<std::string> fillers = {"a", "\xC3\xA4", "\xE2\x82\xAC", "\xF0\x9F\x98\x80"};
|
||||
|
||||
const auto make_string = [](const std::string & filler, const std::size_t before, const std::string & pattern, const std::size_t after)
|
||||
{
|
||||
std::string s;
|
||||
for (std::size_t k = 0; k < before; ++k)
|
||||
{
|
||||
s += filler;
|
||||
}
|
||||
s += pattern;
|
||||
for (std::size_t k = 0; k < after; ++k)
|
||||
{
|
||||
s += filler;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
SECTION("the reference")
|
||||
{
|
||||
// the reference itself, against hand-written results
|
||||
const std::string s = "a\xFF\n\xC3\"\xE0\x80\xF0\x9F\x98\x80\xE2\x82";
|
||||
CHECK(reference_dump_string(s, false, json::error_handler_t::strict).text == "[json.exception.type_error.316] invalid UTF-8 byte at index 1: 0xFF");
|
||||
CHECK(reference_dump_string(s.substr(2), false, json::error_handler_t::strict).text == "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0x22");
|
||||
CHECK(reference_dump_string("\xF0\x9F\x98\x80\xE2\x82", true, json::error_handler_t::strict).text == "[json.exception.type_error.316] incomplete UTF-8 string; last byte: 0x82");
|
||||
CHECK(reference_dump_string(s, false, json::error_handler_t::ignore).text == "\"a\\n\\\"\xF0\x9F\x98\x80\"");
|
||||
CHECK(reference_dump_string(s, true, json::error_handler_t::ignore).text == "\"a\\n\\\"\\ud83d\\ude00\"");
|
||||
CHECK(reference_dump_string(s, false, json::error_handler_t::replace).text == "\"a\xEF\xBF\xBD\\n\xEF\xBF\xBD\\\"\xEF\xBF\xBD\xEF\xBF\xBD\xF0\x9F\x98\x80\xEF\xBF\xBD\"");
|
||||
CHECK(reference_dump_string(s, true, json::error_handler_t::replace).text == "\"a\\ufffd\\n\\ufffd\\\"\\ufffd\\ufffd\\ud83d\\ude00\\ufffd\"");
|
||||
CHECK(reference_dump_string(s, false, json::error_handler_t::keep).text == "\"" + std::string("a\xFF\\n\xC3\\\"\xE0\x80\xF0\x9F\x98\x80\xE2\x82") + "\"");
|
||||
CHECK(reference_dump_string(s, true, json::error_handler_t::keep).text == "\"" + std::string("a\xFF\\n\xC3\\\"\xE0\x80\\ud83d\\ude00\xE2\x82") + "\"");
|
||||
check_dump_against_reference(s);
|
||||
}
|
||||
|
||||
SECTION("short strings")
|
||||
{
|
||||
// strings of up to 17 bytes put the pattern at every offset of an
|
||||
// 8-byte stride of the bulk scanners, with whole strides on both
|
||||
// sides; lengths around 24, 32, and 40 bytes cross the 16- and 32-byte
|
||||
// strides; error_handler_t::strict is checked for up to 17 bytes of
|
||||
// ASCII and 8 bytes of other characters only, as throwing is slow on
|
||||
// some platforms
|
||||
for (const auto& filler : fillers)
|
||||
{
|
||||
for (std::size_t length = 0; length * filler.size() <= 40; ++length)
|
||||
{
|
||||
const std::size_t bytes = length * filler.size();
|
||||
if (filler.size() == 1 && bytes > 17 && (bytes + 1) % 8 > 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (std::size_t pos = 0; pos <= length; ++pos)
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
check_dump_against_reference(make_string(filler, pos, pattern, length - pos), bytes <= (filler.size() == 1 ? 17 : 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("strings crossing the end of the string buffer")
|
||||
{
|
||||
// characters that are not copied in bulk go through a 512-byte string
|
||||
// buffer that is written out once fewer than 13 bytes are left; with
|
||||
// ensure_ascii, each 2-byte character takes 6 bytes of it and each
|
||||
// 4-byte character 12, so the pattern meets the end of the buffer after
|
||||
// about 84 or 42 characters (3-byte characters behave like 2-byte
|
||||
// ones); plain ASCII, copied in bulk, is checked around 512 bytes;
|
||||
// positions further away are sampled; error_handler_t::strict throws
|
||||
// at the first ill-formed byte, regardless of the string buffer, and is
|
||||
// covered above
|
||||
for (const auto& filler : fillers)
|
||||
{
|
||||
if (filler.size() == 3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t escaped_size = filler.size() == 1 ? 1 : filler.size() * 3;
|
||||
const std::size_t middle = 506 / escaped_size;
|
||||
const std::size_t window = filler.size() == 1 ? 8 : 16;
|
||||
for (std::size_t pos = 0; pos <= middle + window; pos += (pos + window >= middle) ? 1 : 29)
|
||||
{
|
||||
for (const std::size_t after :
|
||||
{
|
||||
std::size_t{0}, std::size_t{12}
|
||||
})
|
||||
{
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
check_dump_against_reference(make_string(filler, pos, pattern, after), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("repeated invalid sequences at every offset of the string buffer")
|
||||
{
|
||||
// ill-formed sequences that are kept or replaced fill the string
|
||||
// buffer without a bulk copy in between; a prefix of 0 to 12 bytes
|
||||
// 0xFF (kept as 1 byte, replaced by 3 or 6) shifts them across every
|
||||
// offset of the end of the buffer
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
for (std::size_t shift = 0; shift <= 12; ++shift)
|
||||
{
|
||||
std::string s(shift, '\xFF');
|
||||
while (s.size() < 600)
|
||||
{
|
||||
s += pattern;
|
||||
}
|
||||
check_dump_against_reference(s, shift == 0);
|
||||
check_dump_against_reference(s + "\xF0\x9F\x98\x80", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <string>
|
||||
|
||||
// The exceptions in hot core functions are constructed out of line. These tests pin the
|
||||
// exact what() texts of the sites that moved, so the refactoring cannot change them.
|
||||
|
||||
#define CHECK_MSG(expr, msg) CHECK_THROWS_WITH(expr, msg)
|
||||
|
||||
TEST_CASE("exception messages of out-of-line throw sites")
|
||||
{
|
||||
json arr = {1, 2, 3};
|
||||
json arr2 = {4, 5};
|
||||
json obj = {{"a", 1}, {"b", 2}};
|
||||
json obj2 = {{"c", 3}};
|
||||
json num = 1;
|
||||
|
||||
SECTION("iterators")
|
||||
{
|
||||
CHECK_MSG((void)(arr.begin() == arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
|
||||
CHECK_MSG((void)(arr.begin() < arr2.begin()), "[json.exception.invalid_iterator.212] cannot compare iterators of different containers");
|
||||
CHECK_MSG((void)(obj.begin() < obj.end()), "[json.exception.invalid_iterator.213] cannot compare order of object iterators");
|
||||
CHECK_MSG(obj.begin() += 1, "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
|
||||
CHECK_MSG((void)(obj.begin() - 1), "[json.exception.invalid_iterator.209] cannot use offsets with object iterators");
|
||||
CHECK_MSG((void)obj.begin()[0], "[json.exception.invalid_iterator.208] cannot use operator[] for object iterators");
|
||||
CHECK_MSG((void)arr.begin().key(), "[json.exception.invalid_iterator.207] cannot use key() for non-object iterators");
|
||||
json null;
|
||||
CHECK_MSG((void)*null.end(), "[json.exception.invalid_iterator.214] cannot get value");
|
||||
}
|
||||
|
||||
SECTION("constructors")
|
||||
{
|
||||
CHECK_MSG(json(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.201] iterators are not compatible");
|
||||
CHECK_MSG(json(num.begin() + 1, num.end() + 1), "[json.exception.invalid_iterator.204] iterators out of range");
|
||||
CHECK_MSG(json(json::initializer_list_t{1, 2}, false, json::value_t::object), "[json.exception.type_error.301] cannot create object from initializer list");
|
||||
}
|
||||
|
||||
SECTION("get_ref and operator[]")
|
||||
{
|
||||
CHECK_MSG((void)num.get_ref<std::string&>(), "[json.exception.type_error.303] incompatible ReferenceType for get_ref, actual type is number");
|
||||
CHECK_MSG((void)num[0], "[json.exception.type_error.305] cannot use operator[] with a numeric argument with number");
|
||||
}
|
||||
|
||||
SECTION("at")
|
||||
{
|
||||
const json carr = arr;
|
||||
const json cobj = obj;
|
||||
CHECK_MSG((void)arr.at(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
|
||||
CHECK_MSG((void)carr.at(static_cast<json::size_type>(3)), "[json.exception.out_of_range.401] array index 3 is out of range");
|
||||
CHECK_MSG((void)num.at(static_cast<json::size_type>(0)), "[json.exception.type_error.304] cannot use at() with number");
|
||||
CHECK_MSG((void)obj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)cobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)obj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
|
||||
CHECK_MSG((void)num.at("a"), "[json.exception.type_error.304] cannot use at() with number");
|
||||
|
||||
nlohmann::ordered_json oobj = {{"a", 1}};
|
||||
const nlohmann::ordered_json coobj = oobj;
|
||||
CHECK_MSG((void)oobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)coobj.at("z"), "[json.exception.out_of_range.403] key 'z' not found");
|
||||
CHECK_MSG((void)coobj.at(std::string("zz")), "[json.exception.out_of_range.403] key 'zz' not found");
|
||||
}
|
||||
|
||||
SECTION("erase")
|
||||
{
|
||||
CHECK_MSG(arr.erase(arr2.begin()), "[json.exception.invalid_iterator.202] iterator does not fit current value");
|
||||
CHECK_MSG(num.erase(num.end()), "[json.exception.invalid_iterator.205] iterator out of range");
|
||||
CHECK_MSG(arr.erase(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.203] iterators do not fit current value");
|
||||
CHECK_MSG(num.erase(num.begin() + 1, num.end()), "[json.exception.invalid_iterator.204] iterators out of range");
|
||||
CHECK_MSG(arr.erase(static_cast<json::size_type>(10)), "[json.exception.out_of_range.401] array index 10 is out of range");
|
||||
}
|
||||
|
||||
SECTION("insert")
|
||||
{
|
||||
CHECK_MSG(arr.insert(arr2.begin(), 1), "[json.exception.invalid_iterator.202] iterator does not fit current value");
|
||||
CHECK_MSG(arr.insert(arr2.begin(), 2, 1), "[json.exception.invalid_iterator.202] iterator does not fit current value");
|
||||
CHECK_MSG(arr.insert(arr2.begin(), arr2.begin(), arr2.end()), "[json.exception.invalid_iterator.202] iterator does not fit current value");
|
||||
CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
||||
CHECK_MSG(arr.insert(arr.begin(), arr.begin(), arr.end()), "[json.exception.invalid_iterator.211] passed iterators may not belong to container");
|
||||
CHECK_MSG(arr.insert(arr.begin(), obj.begin(), obj.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to arrays");
|
||||
CHECK_MSG(arr.insert(arr2.begin(), {1, 2}), "[json.exception.invalid_iterator.202] iterator does not fit current value");
|
||||
CHECK_MSG(obj.insert(obj2.begin(), arr.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
||||
CHECK_MSG(obj.insert(arr.begin(), arr.end()), "[json.exception.invalid_iterator.202] iterators first and last must point to objects");
|
||||
CHECK_MSG(obj.update(arr.begin(), arr2.end()), "[json.exception.invalid_iterator.210] iterators do not fit");
|
||||
}
|
||||
|
||||
SECTION("patch")
|
||||
{
|
||||
const auto patch_msg = [](const char* doc, const char* patch)
|
||||
{
|
||||
try
|
||||
{
|
||||
json d = json::parse(doc);
|
||||
d.patch(json::parse(patch));
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
// with JSON_DIAGNOSTICS, what() names the offending value like "(/0) "; drop it
|
||||
std::string msg = e.what();
|
||||
const auto pos = msg.find(" (/");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
msg.erase(pos + 1, msg.find(") ", pos) - pos + 1);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return std::string();
|
||||
};
|
||||
|
||||
CHECK(patch_msg("{}", "{}") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
|
||||
CHECK(patch_msg("{}", "[1]") == "[json.exception.parse_error.104] parse error: JSON patch must be an array of objects");
|
||||
CHECK(patch_msg("{}", "[{}]") == "[json.exception.parse_error.105] parse error: operation must have member 'op'");
|
||||
CHECK(patch_msg("{}", "[{\"op\":1}]") == "[json.exception.parse_error.105] parse error: operation must have string member 'op'");
|
||||
CHECK(patch_msg("{}", "[{\"op\":\"add\"}]") == "[json.exception.parse_error.105] parse error: operation 'add' must have member 'path'");
|
||||
CHECK(patch_msg("{}", "[{\"op\":\"add\",\"path\":1}]") == "[json.exception.parse_error.105] parse error: operation 'add' must have string member 'path'");
|
||||
CHECK(patch_msg("{}", "[{\"op\":\"foo\",\"path\":\"\"}]") == "[json.exception.parse_error.105] parse error: operation value 'foo' is invalid");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"remove\",\"path\":\"/b\"}]") == "[json.exception.out_of_range.403] key 'b' not found");
|
||||
CHECK(patch_msg("[1]", "[{\"op\":\"add\",\"path\":\"/5\",\"value\":0}]") == "[json.exception.out_of_range.401] array index 5 is out of range");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"add\",\"path\":\"/a/b\",\"value\":0}]") == "[json.exception.out_of_range.411] cannot add value: the JSON Patch 'add' target's parent is of type number, but must be an object or array");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"remove\",\"path\":\"/a/b\"}]") == "[json.exception.out_of_range.413] cannot remove value: the JSON Patch 'remove' target's parent is of type number, but must be an object or array");
|
||||
CHECK(patch_msg("{\"a\":{}}", "[{\"op\":\"move\",\"from\":\"/a\",\"path\":\"/a/b\"}]") == "[json.exception.out_of_range.414] cannot move value: 'from' path '/a' is a proper prefix of 'path' '/a/b'");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"test\",\"path\":\"/a\",\"value\":2}]") == "[json.exception.other_error.501] unsuccessful: {\"op\":\"test\",\"path\":\"/a\",\"value\":2}");
|
||||
CHECK(patch_msg("{\"a\":1}", "[{\"op\":\"move\",\"path\":\"/a\"}]") == "[json.exception.parse_error.105] parse error: operation 'move' must have member 'from'");
|
||||
}
|
||||
}
|
||||
@@ -21,11 +21,8 @@ using nlohmann::detail::dtoa_impl::reinterpret_bits;
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -689,20 +686,13 @@ void check_shortest(double v)
|
||||
const std::string& digits = de.first;
|
||||
if (digits.size() > 1)
|
||||
{
|
||||
// the decimals of one digit fewer next to the value
|
||||
// (a stream rather than snprintf("%.*e"), whose output GCC cannot bound)
|
||||
std::ostringstream shorter;
|
||||
shorter.imbue(std::locale::classic());
|
||||
shorter << std::scientific << std::setprecision(static_cast<int>(digits.size()) - 2) << v;
|
||||
const auto near = digits_and_exponent(shorter.str());
|
||||
// as an integer with digits.size() - 1 digits
|
||||
std::string m = near.first;
|
||||
int e = near.second;
|
||||
while (m.size() < digits.size() - 1)
|
||||
{
|
||||
m += '0';
|
||||
--e;
|
||||
}
|
||||
// the decimals of one digit fewer next to the value: v lies within
|
||||
// half a unit of the last digit of text, so the closest ones are the
|
||||
// truncated digits and their successor
|
||||
// (not a stream with std::setprecision(digits.size() - 2): the
|
||||
// standard library of MSVC 2017 ignores a precision of 0)
|
||||
const std::string m = digits.substr(0, digits.size() - 1);
|
||||
const int e = de.second + 1;
|
||||
const std::uint64_t mid = std::stoull(m);
|
||||
for (const std::uint64_t candidate :
|
||||
{
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <valarray>
|
||||
#include <vector>
|
||||
|
||||
#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1)
|
||||
#if __has_include(<filesystem>)
|
||||
#include <filesystem>
|
||||
#define TO_JSON_RVALUE_HAS_FS 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
// element type that counts how it is converted to JSON: a conversion from an
|
||||
// lvalue copies, a conversion from an rvalue moves
|
||||
struct tracked
|
||||
{
|
||||
std::string payload;
|
||||
bool moved_from = false;
|
||||
|
||||
explicit tracked(std::string p) : payload(std::move(p)) {}
|
||||
tracked(const tracked&) = default;
|
||||
tracked& operator=(const tracked&) = default;
|
||||
tracked(tracked&& other) noexcept : payload(std::move(other.payload)), moved_from(other.moved_from)
|
||||
{
|
||||
other.moved_from = true;
|
||||
}
|
||||
tracked& operator=(tracked&& other) noexcept
|
||||
{
|
||||
payload = std::move(other.payload);
|
||||
moved_from = other.moved_from;
|
||||
other.moved_from = true;
|
||||
return *this;
|
||||
}
|
||||
~tracked() = default;
|
||||
};
|
||||
|
||||
int tracked_copies = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
|
||||
int tracked_moves = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,misc-use-internal-linkage)
|
||||
|
||||
void to_json(json& j, const tracked& t)
|
||||
{
|
||||
++tracked_copies;
|
||||
j = t.payload;
|
||||
}
|
||||
|
||||
void to_json(json& j, tracked&& t)
|
||||
{
|
||||
++tracked_moves;
|
||||
j = std::move(t.payload);
|
||||
t.moved_from = true;
|
||||
}
|
||||
|
||||
// user type with its own rvalue-aware serializer
|
||||
struct udt_container
|
||||
{
|
||||
std::vector<int> values;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace nlohmann
|
||||
{
|
||||
template<>
|
||||
struct adl_serializer<udt_container>
|
||||
{
|
||||
static void to_json(json& j, const udt_container& c)
|
||||
{
|
||||
j = c.values;
|
||||
j.push_back("copied");
|
||||
}
|
||||
static void to_json(json& j, udt_container&& c)
|
||||
{
|
||||
j = std::move(c.values);
|
||||
j.push_back("moved");
|
||||
}
|
||||
};
|
||||
} // namespace nlohmann
|
||||
|
||||
TEST_CASE("to_json from rvalue containers")
|
||||
{
|
||||
const std::string long_a(64, 'a');
|
||||
const std::string long_b(64, 'b');
|
||||
|
||||
SECTION("vector<std::string>: elements are moved")
|
||||
{
|
||||
std::vector<std::string> v{long_a, long_b};
|
||||
const json j = std::move(v);
|
||||
CHECK(j == json::array({long_a, long_b}));
|
||||
for (const auto& s : v) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
{
|
||||
CHECK(s.empty()); // long strings leave the source element empty when moved
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("vector<std::string>: lvalues are copied and left untouched")
|
||||
{
|
||||
const std::vector<std::string> v{long_a, long_b};
|
||||
const json j = v;
|
||||
CHECK(j == json::array({long_a, long_b}));
|
||||
CHECK(v[0] == long_a);
|
||||
CHECK(v[1] == long_b);
|
||||
|
||||
std::vector<std::string> v2{long_a, long_b};
|
||||
const json j2 = v2;
|
||||
CHECK(j2 == j);
|
||||
CHECK(v2[0] == long_a);
|
||||
CHECK(v2[1] == long_b);
|
||||
}
|
||||
|
||||
SECTION("tracked elements: rvalue moves, lvalue copies")
|
||||
{
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
|
||||
std::vector<tracked> v;
|
||||
v.emplace_back("x");
|
||||
v.emplace_back("y");
|
||||
|
||||
const json copy = v;
|
||||
CHECK(copy == json::array({"x", "y"}));
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 0);
|
||||
CHECK_FALSE(v[0].moved_from);
|
||||
|
||||
const json moved = std::move(v);
|
||||
CHECK(moved == copy);
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 2);
|
||||
}
|
||||
|
||||
SECTION("const rvalue is copied")
|
||||
{
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
const std::vector<tracked> v(2, tracked("z"));
|
||||
const json j = std::move(v); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
|
||||
CHECK(j == json::array({"z", "z"}));
|
||||
CHECK(tracked_copies == 2);
|
||||
CHECK(tracked_moves == 0);
|
||||
}
|
||||
|
||||
SECTION("other sequence containers")
|
||||
{
|
||||
CHECK(json(std::deque<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::forward_list<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::array<std::string, 2> {{long_a, long_b}}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::set<std::string> {long_a, long_b}) == json::array({long_a, long_b}));
|
||||
CHECK(json(std::vector<bool> {true, false}) == json::array({true, false}));
|
||||
CHECK(json(std::vector<int> {1, 2, 3}) == json::array({1, 2, 3}));
|
||||
CHECK(json(std::vector<std::vector<std::string>> {{long_a}, {long_b}}) == json::array({json::array({long_a}), json::array({long_b})}));
|
||||
CHECK(json(std::vector<json> {1, "a"}) == json::array({1, "a"}));
|
||||
}
|
||||
|
||||
SECTION("valarray")
|
||||
{
|
||||
std::valarray<int> va(3);
|
||||
va[0] = 1;
|
||||
va[1] = 2;
|
||||
va[2] = 3;
|
||||
CHECK(json(std::move(va)) == json::array({1, 2, 3})); // NOLINT(performance-move-const-arg,hicpp-move-const-arg)
|
||||
CHECK(json(std::valarray<int>(2)) == json::array({0, 0}));
|
||||
}
|
||||
|
||||
SECTION("array_t and json arrays")
|
||||
{
|
||||
json::array_t arr{1, 2};
|
||||
CHECK(json(std::move(arr)) == json::array({1, 2}));
|
||||
json a = json::array({1, 2});
|
||||
const json b = std::move(a);
|
||||
CHECK(b == json::array({1, 2}));
|
||||
}
|
||||
|
||||
SECTION("object-like containers")
|
||||
{
|
||||
std::map<std::string, std::string> m{{"a", long_a}, {"b", long_b}};
|
||||
const json j = std::move(m);
|
||||
CHECK(j == json({{"a", long_a}, {"b", long_b}}));
|
||||
for (const auto& kv : m) // NOLINT(bugprone-use-after-move,hicpp-invalid-access-moved)
|
||||
{
|
||||
CHECK(kv.second.empty()); // values moved, keys (const) copied
|
||||
}
|
||||
|
||||
const std::map<std::string, std::string> cm{{"a", long_a}};
|
||||
const json jc = cm;
|
||||
CHECK(jc["a"] == long_a);
|
||||
CHECK(cm.at("a") == long_a);
|
||||
|
||||
std::unordered_map<std::string, std::string> um{{"a", long_a}};
|
||||
CHECK(json(std::move(um)) == json({{"a", long_a}}));
|
||||
|
||||
std::multimap<std::string, int> mm{{"a", 1}};
|
||||
CHECK(json(std::move(mm)) == json({{"a", 1}}));
|
||||
|
||||
tracked_copies = 0;
|
||||
tracked_moves = 0;
|
||||
std::map<std::string, tracked> mt;
|
||||
mt.emplace("k", tracked("v"));
|
||||
const json jt = std::move(mt);
|
||||
CHECK(jt == json({{"k", "v"}}));
|
||||
CHECK(tracked_moves == 1);
|
||||
CHECK(tracked_copies == 0);
|
||||
|
||||
json::object_t obj{{"a", 1}};
|
||||
CHECK(json(std::move(obj)) == json({{"a", 1}}));
|
||||
}
|
||||
|
||||
SECTION("user type with its own to_json overloads")
|
||||
{
|
||||
udt_container c{{1, 2}};
|
||||
const json lv = c;
|
||||
CHECK(lv == json::array({1, 2, "copied"}));
|
||||
const json rv = std::move(c);
|
||||
CHECK(rv == json::array({1, 2, "moved"}));
|
||||
}
|
||||
|
||||
SECTION("assignment and implicit conversion")
|
||||
{
|
||||
std::vector<std::string> v{long_a};
|
||||
json j;
|
||||
j = std::move(v);
|
||||
CHECK(j == json::array({long_a}));
|
||||
json k = {std::vector<std::string>{long_b}};
|
||||
CHECK(k.size() == 1);
|
||||
}
|
||||
|
||||
#ifdef TO_JSON_RVALUE_HAS_FS
|
||||
SECTION("std::filesystem::path rvalue is still a string")
|
||||
{
|
||||
CHECK(json(std::filesystem::path("a/b")) == json("a/b"));
|
||||
#ifdef __cpp_lib_char8_t
|
||||
CHECK(json(std::u8string(u8"a/b")) == json("a/b"));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user