mirror of
https://github.com/nlohmann/json.git
synced 2026-10-15 18:57:13 +00:00
Compare commits
27
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85d83bed74 | ||
|
|
b41228c227 | ||
|
|
dcb96e82fb | ||
|
|
c7fd5ac19f | ||
|
|
a678aa6dd7 | ||
|
|
3d93f682a7 | ||
|
|
1c696dcf05 | ||
|
|
7cd34c62a9 | ||
|
|
eb74fe2ab7 | ||
|
|
91b9ee2602 | ||
|
|
ce28c722bc | ||
|
|
6261d1e432 | ||
|
|
ed86019a21 | ||
|
|
4f11512641 | ||
|
|
6a27b9acb2 | ||
|
|
91869983a0 | ||
|
|
4530d9681c | ||
|
|
ca7740fef2 | ||
|
|
6ba53e63dd | ||
|
|
0a4b3a84ee | ||
|
|
0c4e7c103c | ||
|
|
a6e9785ab6 | ||
|
|
0905602724 | ||
|
|
15b0ff3be6 | ||
|
|
e4cb826600 | ||
|
|
9dc60905ed | ||
|
|
467670aa7a |
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
|
||||
@@ -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.
|
||||
|
||||
@@ -6,6 +6,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
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ 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
|
||||
@@ -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.
|
||||
@@ -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
@@ -9,7 +9,10 @@
|
||||
#include <benchmark/benchmark.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <test_data.hpp>
|
||||
|
||||
@@ -621,4 +624,27 @@ static void FromCborChunkedString(benchmark::State& state, const std::size_t chu
|
||||
|
||||
BENCHMARK_CAPTURE(FromCborChunkedString, 10000 chunks, 10000);
|
||||
|
||||
// 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&);
|
||||
}
|
||||
@@ -0,0 +1,474 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | 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
|
||||
|
||||
// The fast paths for contiguous input (bulk string scanning, SWAR searches,
|
||||
// UTF-8 prefix validation, the 8-digit number loop, the 8-byte indentation
|
||||
// skip, the BSON/BON8 bulk string readers) load several bytes at a time. This
|
||||
// test places every input directly in front of (or directly behind) an
|
||||
// inaccessible page, so reading even one byte outside the input crashes, with
|
||||
// or without sanitizers. ASan alone misses over-reads into the slack of the
|
||||
// same heap allocation.
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define JSON_TEST_GUARD_PAGE_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
#define JSON_TEST_GUARD_PAGE_POSIX
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS) || defined(JSON_TEST_GUARD_PAGE_POSIX)
|
||||
|
||||
namespace
|
||||
{
|
||||
// Three pages: an inaccessible one, a readable one, an inaccessible one. An
|
||||
// input is copied either to the end of the middle page (any read past its last
|
||||
// byte faults) or to its start (any read before its first byte faults).
|
||||
class guarded_buffer
|
||||
{
|
||||
public:
|
||||
guarded_buffer()
|
||||
: page(page_size())
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
void* const p = VirtualAlloc(nullptr, 3 * page, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
|
||||
REQUIRE(p != nullptr);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
DWORD old_protect = 0;
|
||||
REQUIRE(VirtualProtect(base + page, page, PAGE_READWRITE, &old_protect) != 0);
|
||||
#else
|
||||
void* const p = mmap(nullptr, 3 * page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
REQUIRE(p != MAP_FAILED);
|
||||
base = static_cast<unsigned char*>(p);
|
||||
REQUIRE(mprotect(base + page, page, PROT_READ | PROT_WRITE) == 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
~guarded_buffer()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
VirtualFree(base, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(base, 3 * page);
|
||||
#endif
|
||||
}
|
||||
|
||||
guarded_buffer(const guarded_buffer&) = delete;
|
||||
guarded_buffer& operator=(const guarded_buffer&) = delete;
|
||||
guarded_buffer(guarded_buffer&&) = delete;
|
||||
guarded_buffer& operator=(guarded_buffer&&) = delete;
|
||||
|
||||
// copies @a input so its last byte is the last readable byte
|
||||
const std::uint8_t* at_end(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + (2 * page) - input.size();
|
||||
std::memcpy(first, input.data(), input.size());
|
||||
return first;
|
||||
}
|
||||
|
||||
// copies @a input so its first byte is the first readable byte
|
||||
const std::uint8_t* at_start(const std::string& input)
|
||||
{
|
||||
REQUIRE(input.size() <= page);
|
||||
unsigned char* const first = base + page;
|
||||
std::memcpy(first, input.data(), input.size());
|
||||
return first;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::size_t page_size()
|
||||
{
|
||||
#if defined(JSON_TEST_GUARD_PAGE_WINDOWS)
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return static_cast<std::size_t>(info.dwPageSize);
|
||||
#else
|
||||
return static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t page = 0;
|
||||
unsigned char* base = nullptr;
|
||||
};
|
||||
|
||||
// the serialization of the result of @a f, or the exception message; @a f
|
||||
// takes the allow_exceptions flag, which is false without exceptions, where
|
||||
// an error yields a discarded value instead
|
||||
template<typename F>
|
||||
std::string outcome(F f)
|
||||
{
|
||||
#if defined(JSON_NOEXCEPTION)
|
||||
return "value: " + f(false).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
#else
|
||||
try
|
||||
{
|
||||
return "value: " + f(true).dump(-1, ' ', false, json::error_handler_t::keep);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
return std::string("error: ") + e.what();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// a SAX handler that only counts events, so sax_parse runs without a DOM
|
||||
struct counting_sax : nlohmann::json_sax<json>
|
||||
{
|
||||
std::size_t events = 0;
|
||||
std::string error{}; // NOLINT(readability-redundant-member-init)
|
||||
|
||||
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 nlohmann::detail::exception& ex) override
|
||||
{
|
||||
error = std::to_string(position) + " " + ex.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool count()
|
||||
{
|
||||
++events;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// all prefixes of each seed, so the input ends at every byte of it
|
||||
std::vector<std::string> all_prefixes(const std::vector<std::string>& seeds)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (const auto& seed : seeds)
|
||||
{
|
||||
for (std::size_t n = 0; n <= seed.size(); ++n)
|
||||
{
|
||||
result.push_back(seed.substr(0, n));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> json_seeds()
|
||||
{
|
||||
std::vector<std::string> seeds =
|
||||
{
|
||||
// string runs: ASCII, escapes, \u escapes, surrogate pairs
|
||||
"\"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"",
|
||||
R"("ab\ncd\"ef\\gh\u00e4ij\uD83D\uDE00kl\u12")",
|
||||
// 2-, 3- and 4-byte UTF-8 sequences, then invalid UTF-8
|
||||
std::string("\"\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80") + "abcdefgh\"",
|
||||
"\"abcdefgh\xC3\x28 \xED\xA0\x80 \xF4\x90\x80\x80 \xF8 \xC0\xAF\"",
|
||||
std::string("\"abcdefg\x01") + "abcdefgh\"",
|
||||
// numbers, so the 8-digit loop runs up to the end
|
||||
"12345678",
|
||||
"1234567890123456",
|
||||
"12345678901234567890123",
|
||||
"-0.000000001234567890123456789",
|
||||
"3.14159265358979323846264338327950288e+100",
|
||||
"-1.2345678901234567e-12",
|
||||
"1E-5",
|
||||
"[1.5,2e10,-3E-5,0.000000001,123456789012345678]",
|
||||
// literals
|
||||
"[true,false,null]",
|
||||
// comments (only parsed with ignore_comments)
|
||||
"/* comment */ [1, // line comment\n 2] /**/",
|
||||
// a pretty-printed document
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(4),
|
||||
json::parse(R"({"a": [1, 2.5, "three", {"b": null, "c": [true, false]}], "d": {"e": {"f": {"g": "h"}}}})").dump(8, '\t'),
|
||||
};
|
||||
|
||||
// indentation runs of every length, with and without a closing bracket
|
||||
for (std::size_t n = 0; n <= 20; ++n)
|
||||
{
|
||||
seeds.push_back("[\n" + std::string(n, ' ') + "1,\n" + std::string(n, ' ') + "2\n]");
|
||||
}
|
||||
|
||||
return all_prefixes(seeds);
|
||||
}
|
||||
|
||||
// a value whose binary serializations contain long strings and keys, numbers
|
||||
// of every width, and nested containers
|
||||
json binary_document()
|
||||
{
|
||||
json j;
|
||||
j["a key that is longer than eight bytes"] = "a string value that is longer than eight bytes";
|
||||
j["\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80"] = "\xF0\x9F\x98\x80\xE2\x82\xAC\xC3\xA4 utf-8 0123456789";
|
||||
j["numbers"] = {0, 1, -1, 255, -256, 65536, -2147483649LL, 4294967296ULL, 1.5, -2.25e100};
|
||||
j["nested"] = {{"x", {{"y", {true, false, nullptr}}}}};
|
||||
j["s"] = "";
|
||||
return j;
|
||||
}
|
||||
|
||||
// readers for check_binary(), callable with a container or an iterator range
|
||||
// followed by the allow_exceptions flag
|
||||
#define JSON_TEST_GUARD_PAGE_READER(name, from) \
|
||||
struct name \
|
||||
{ \
|
||||
template<typename... Args> \
|
||||
json operator()(bool allow_exceptions, Args&& ... args) const \
|
||||
{ \
|
||||
return json::from(std::forward<Args>(args)..., true, allow_exceptions); \
|
||||
} \
|
||||
};
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bson, from_bson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bon8, from_bon8)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_cbor, from_cbor)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_msgpack, from_msgpack)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_ubjson, from_ubjson)
|
||||
JSON_TEST_GUARD_PAGE_READER(read_bjdata, from_bjdata)
|
||||
#undef JSON_TEST_GUARD_PAGE_READER
|
||||
|
||||
// reads every prefix of @a bytes at both ends of the guarded page and from a
|
||||
// std::string, which must give the same outcome
|
||||
template<typename Reader>
|
||||
void check_binary(guarded_buffer& guard, const std::vector<std::uint8_t>& bytes, Reader parse)
|
||||
{
|
||||
for (const auto& input : all_prefixes({std::string(bytes.begin(), bytes.end())}))
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, input);
|
||||
});
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return parse(allow, first, first + input.size());
|
||||
}) == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse_float_native() at both ends of the guarded page, compared bit for bit
|
||||
// with converting the same token from a std::string
|
||||
template<typename FloatType>
|
||||
void check_float_native(guarded_buffer& guard, const std::string& token)
|
||||
{
|
||||
const std::size_t decimal_point = token.find('.');
|
||||
const std::size_t exponent = token.find_first_of("eE");
|
||||
const std::size_t mantissa_end = exponent == std::string::npos ? token.size() : exponent;
|
||||
const FloatType expected = nlohmann::detail::parse_float_native<FloatType>(token.data(), token.data() + token.size(), decimal_point, mantissa_end);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const char* const first = reinterpret_cast<const char*>(placement == 0 ? guard.at_end(token) : guard.at_start(token));
|
||||
const FloatType value = nlohmann::detail::parse_float_native<FloatType>(first, first + token.size(), decimal_point, mantissa_end);
|
||||
CHECK(std::memcmp(&value, &expected, sizeof(value)) == 0);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("guard page")
|
||||
{
|
||||
guarded_buffer guard;
|
||||
|
||||
SECTION("string scanning")
|
||||
{
|
||||
// fills that drive each function to a different stop
|
||||
const std::vector<std::string> fills =
|
||||
{
|
||||
std::string(64, 'a'),
|
||||
std::string(64, '"'),
|
||||
std::string(64, '\\'),
|
||||
std::string(64, '\x01'),
|
||||
std::string(64, '\x7F'),
|
||||
std::string(64, '\xFF'),
|
||||
};
|
||||
std::vector<std::string> patterns = fills;
|
||||
{
|
||||
std::string utf8;
|
||||
while (utf8.size() < 64)
|
||||
{
|
||||
utf8 += "\xC3\xA4\xE2\x82\xAC\xF0\x9F\x98\x80" "ab";
|
||||
}
|
||||
patterns.push_back(utf8);
|
||||
}
|
||||
{
|
||||
std::string bytes;
|
||||
for (unsigned int i = 0; i < 64; ++i)
|
||||
{
|
||||
bytes.push_back(static_cast<char>((i * 37u) & 0xFFu));
|
||||
}
|
||||
patterns.push_back(bytes);
|
||||
}
|
||||
|
||||
for (const auto& pattern : patterns)
|
||||
{
|
||||
for (std::size_t n = 0; n <= 64; ++n)
|
||||
{
|
||||
const std::string input = pattern.substr(0, n);
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const unsigned char* const p = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
|
||||
CHECK(nlohmann::detail::find_string_special(p, n) <= n);
|
||||
CHECK(nlohmann::detail::find_ascii_copyable_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::valid_utf8_prefix(p, n) <= n);
|
||||
#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17)
|
||||
CHECK(nlohmann::detail::find_string_delimiter(p, n) <= n);
|
||||
#endif
|
||||
CHECK(nlohmann::detail::string_bulk_run(p, n) <= n);
|
||||
CHECK(nlohmann::detail::scalar_string_bulk_run(p, n) <= n);
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
CHECK(nlohmann::detail::validate_one_utf8(p + i, n - i) <= n - i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("float conversion")
|
||||
{
|
||||
// the lexer converts floats from its token buffer, so the JSON section
|
||||
// below never places a float token at the page boundary; this calls the
|
||||
// conversion directly: the 8-digit loops of parse_float_significand()
|
||||
// and digit_comparison() read up to the end of the token
|
||||
const std::vector<std::string> tokens = all_prefixes(
|
||||
{
|
||||
"1234567812345678123456781234567812345678",
|
||||
"-1.2345678901234567890123456789e-123",
|
||||
"0.00000000000000000000000000000000000001",
|
||||
"98765432.10987654",
|
||||
"123456789012345678901234567890e-30",
|
||||
// halfway between 1 and the next double, then just above it
|
||||
"1.00000000000000011102230246251565404236316680908203125",
|
||||
"1.000000000000000111022302462515654042363166809082031250000000001",
|
||||
});
|
||||
for (const auto& token : tokens)
|
||||
{
|
||||
// only complete number tokens: the conversion expects validated input
|
||||
if (!token.empty() && json::accept(token))
|
||||
{
|
||||
CAPTURE(token)
|
||||
check_float_native<double>(guard, token);
|
||||
check_float_native<float>(guard, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("JSON")
|
||||
{
|
||||
for (const auto& input : json_seeds())
|
||||
{
|
||||
CAPTURE(input)
|
||||
const std::string expected = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow);
|
||||
});
|
||||
const std::string expected_comments = outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(input, nullptr, allow, true);
|
||||
});
|
||||
const bool expected_accept = json::accept(input);
|
||||
counting_sax expected_sax;
|
||||
const bool expected_sax_result = json::sax_parse(input, &expected_sax);
|
||||
|
||||
for (int placement = 0; placement < 2; ++placement)
|
||||
{
|
||||
const std::uint8_t* const first = placement == 0 ? guard.at_end(input) : guard.at_start(input);
|
||||
const std::uint8_t* const last = first + input.size();
|
||||
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow);
|
||||
}) == expected);
|
||||
CHECK(outcome([&](bool allow)
|
||||
{
|
||||
return json::parse(first, last, nullptr, allow, true);
|
||||
}) == expected_comments);
|
||||
CHECK(json::accept(first, last) == expected_accept);
|
||||
|
||||
counting_sax sax;
|
||||
CHECK(json::sax_parse(first, last, &sax) == expected_sax_result);
|
||||
CHECK(sax.events == expected_sax.events);
|
||||
CHECK(sax.error == expected_sax.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("binary formats")
|
||||
{
|
||||
// every prefix ends inside a value, e.g., inside a BSON key or BON8
|
||||
// string without its terminator
|
||||
const json j = binary_document();
|
||||
check_binary(guard, json::to_bson(j), read_bson());
|
||||
check_binary(guard, json::to_bon8(j), read_bon8());
|
||||
check_binary(guard, json::to_cbor(j), read_cbor());
|
||||
check_binary(guard, json::to_msgpack(j), read_msgpack());
|
||||
check_binary(guard, json::to_ubjson(j), read_ubjson());
|
||||
check_binary(guard, json::to_bjdata(j), read_bjdata());
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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