mirror of
https://github.com/nlohmann/json.git
synced 2026-11-12 15:10:47 +00:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff1025c4e4 | ||
|
|
51abba08fd | ||
|
|
e08c181dee | ||
|
|
c227d4a920 | ||
|
|
bdcfc178af | ||
|
|
30e1adef93 | ||
|
|
afea570755 | ||
|
|
e7d245db7e | ||
|
|
d38a04bd3e |
No files matched your search
@@ -24,11 +24,11 @@ jobs:
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ permissions:
|
||||
jobs:
|
||||
save:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ concurrency:
|
||||
jobs:
|
||||
check_docs_links:
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
comment:
|
||||
# push runs on develop have no PR to comment on (and no "pr" artifact)
|
||||
if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }}
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -18,10 +18,10 @@ permissions:
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ on:
|
||||
jobs:
|
||||
flawfinder:
|
||||
name: Flawfinder
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -48,6 +48,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -34,10 +34,10 @@ jobs:
|
||||
contents: write
|
||||
|
||||
if: github.repository == 'nlohmann/json'
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ permissions:
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# Needed to upload the results to code-scanning dashboard.
|
||||
security-events: write
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
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@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
|
||||
uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
@@ -8,7 +8,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
ci_test_gcc:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -130,7 +130,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_static_analysis_clang:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:22
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
run: cmake --build build --target ${{ matrix.target }}
|
||||
|
||||
ci_cmake_options:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
fail-on-error: false
|
||||
|
||||
ci_test_compilers_gcc_old:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['4.8', '4.9', '5', '6']
|
||||
@@ -251,7 +251,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_g++-${{ matrix.compiler }}
|
||||
|
||||
ci_test_compilers_gcc:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
# older GCC docker images (4, 5, 6) fail to check out code
|
||||
@@ -269,7 +269,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_compilers_clang:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', 'latest']
|
||||
@@ -289,7 +289,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_compiler_default
|
||||
|
||||
ci_test_standards_gcc:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: gcc:latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -306,7 +306,7 @@ jobs:
|
||||
run: cmake --build build --target ci_test_gcc_cxx${{ matrix.standard }}
|
||||
|
||||
ci_test_standards_clang:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: silkeh/clang:latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -330,7 +330,7 @@ jobs:
|
||||
if: ${{ matrix.stdlib == 'libstdcxx' }}
|
||||
|
||||
ci_cuda_example:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -356,7 +356,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
container: ['gcc:latest', 'silkeh/clang:latest']
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: ${{ matrix.container }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -383,7 +383,7 @@ jobs:
|
||||
run: cmake --build build --target ci_module_cpp20
|
||||
|
||||
ci_icpc:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
# Intel discontinued the classic icc/icpc compiler in oneAPI 2024.0; this is
|
||||
# Intel's own last officially published image that still includes it.
|
||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||
@@ -402,7 +402,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpc
|
||||
|
||||
ci_icpx:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: intel/oneapi-hpckit:latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -416,7 +416,7 @@ jobs:
|
||||
run: cmake --build build --target ci_icpx
|
||||
|
||||
ci_nvhpc:
|
||||
runs-on: ubuntu-26.04
|
||||
runs-on: ubuntu-latest
|
||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -433,7 +433,7 @@ jobs:
|
||||
runs-on: ubuntu-26.04
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -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@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
|
||||
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up MinGW
|
||||
uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0
|
||||
uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0
|
||||
with:
|
||||
platform: ${{ matrix.architecture }}
|
||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||
@@ -153,7 +153,7 @@ jobs:
|
||||
- name: Install Clang
|
||||
run: curl -fsSL -o LLVM${{ matrix.version }}.exe https://github.com/llvm/llvm-project/releases/download/llvmorg-${{ matrix.version }}/LLVM-${{ matrix.version }}-win64.exe ; 7z x LLVM${{ matrix.version }}.exe -y -o"C:/Program Files/LLVM"
|
||||
- name: Set up MinGW
|
||||
uses: egor-tensin/setup-mingw@d54f5ef0ce585bd3fbb1f8a71b5b254f59dbf931 # v3.0
|
||||
uses: egor-tensin/setup-mingw@41b837e47d7f85214629d255b9c4bc3fcbe9fd63 # v3.0
|
||||
with:
|
||||
platform: x64
|
||||
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
|
||||
|
||||
@@ -1408,9 +1408,9 @@ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR I
|
||||
- The class contains a port of the shortest double-to-decimal conversion of [Żmij](https://github.com/vitaut/zmij) by Victor Zverovich, including the conversion of the digits to text by Xiang JunBo and the SIMD instruction sequence of Dougall Johnson, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above). Copyright © 2025 [Victor Zverovich](https://github.com/vitaut)
|
||||
- The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
- The class contains parts of [Google Abseil](https://github.com/abseil/abseil-cpp) which is licensed under the [Apache 2.0 License](https://opensource.org/licenses/Apache-2.0).
|
||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
- The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
- The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||
- The class's lexer and the view's parser (`<nlohmann/json_view.hpp>`) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
- The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
|
||||
<img align="right" src="https://git.fsfe.org/reuse/reuse-ci/raw/branch/master/reuse-horizontal.png" alt="REUSE Software">
|
||||
|
||||
|
||||
+3
-3
@@ -348,20 +348,20 @@ add_custom_target(ci_test_no_thread_local
|
||||
|
||||
add_custom_target(ci_test_coverage
|
||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="--coverage;-fprofile-arcs;-ftest-coverage"
|
||||
-DJSON_BuildTests=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND CXX=g++ ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage;-fvar-tracking-assignments"
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja -DCMAKE_CXX_FLAGS="-m32;--coverage;-fprofile-arcs;-ftest-coverage"
|
||||
-DJSON_BuildTests=ON -DJSON_32bitTest=ONLY
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_coverage32
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_coverage32 && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} --directory . --capture --output-file json.info --rc branch_coverage=1 --rc geninfo_unexecuted_blocks=1 --ignore-errors mismatch --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${LCOV_TOOL} -e json.info ${SRC_FILES} --output-file json.info.filtered --rc branch_coverage=1 --ignore-errors unused --ignore-errors inconsistent
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/tests/thirdparty/imapdl/filterbr.py json.info.filtered > json.info.filtered.noexcept
|
||||
COMMAND ${GENHTML_TOOL} --title "JSON for Modern C++" --legend --demangle-cpp --output-directory html --show-details --branch-coverage --ignore-errors inconsistent json.info.filtered.noexcept
|
||||
|
||||
+1
-3
@@ -178,10 +178,8 @@ function(_json_test_add_test test_name file main cxx_standard)
|
||||
COMMAND ${memcheck_command} $<TARGET_FILE:${test_target}> ${DOCTEST_TEST_FILTER}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
)
|
||||
# under Valgrind the slowest tests take close to 25 minutes, so ctest's
|
||||
# default timeout of 1500 seconds leaves no headroom
|
||||
set_tests_properties(${test_target}_valgrind PROPERTIES
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA TIMEOUT 3600
|
||||
LABELS "valgrind" FIXTURES_REQUIRED TEST_DATA
|
||||
)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -90,8 +90,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
1. Constant.
|
||||
2. Logarithmic in the size of the container.
|
||||
3. Logarithmic in the size of the container.
|
||||
4. Linear in the number of reference tokens of `ptr`; for each of them, logarithmic in the size of the object, or
|
||||
constant for an array.
|
||||
4. Logarithmic in the size of the container.
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@ The equality holds up to [`operator==`](operator_eq.md), so it has the following
|
||||
- A value that is not equal to itself cannot round-trip. This applies to `NaN` (also nested in an array or object) and
|
||||
to [discarded](is_discarded.md) values: `diff` emits a `replace` operation for them. A `NaN` in the patch is
|
||||
serialized as `#!json null`, so a patch that was dumped and parsed again would set `#!json null`.
|
||||
- Numbers that compare equal produce no operation, even if their types differ or if one is zero and the other is
|
||||
negative zero: `diff(1, 1.0)` (integer and floating-point), `diff(1u, 1)` (unsigned and signed integer) and
|
||||
`diff(0.0, -0.0)` (positive and negative zero) all return `#!json []`. The patched value is then the `source` and
|
||||
keeps its number type and sign.
|
||||
- Numbers that compare equal produce no operation, even if their types or signs differ: `diff(1, 1.0)`,
|
||||
`diff(1u, 1)` (unsigned and signed integer) and `diff(0.0, -0.0)` all return `#!json []`. The patched value is then
|
||||
the `source` and keeps its number type.
|
||||
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -120,16 +120,6 @@ overload (3).
|
||||
See [Number conversion](../../features/types/number_handling.md#number-conversion)
|
||||
for more information.
|
||||
|
||||
!!! note "Booleans as numbers"
|
||||
|
||||
A JSON boolean converts to `#!cpp 0` or `#!cpp 1` for every arithmetic type **except** [`number_integer_t`](number_integer_t.md),
|
||||
[`number_unsigned_t`](number_unsigned_t.md) and [`number_float_t`](number_float_t.md) (by default `#!cpp std::int64_t`,
|
||||
`#!cpp std::uint64_t`, and `#!cpp double`), for which `#!cpp get<T>()` throws
|
||||
[`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) ("type must be number, but is boolean"). So
|
||||
`#!cpp json(true).get<int>()` is `#!cpp 1`, while `#!cpp json(true).get<std::int64_t>()` and
|
||||
`#!cpp json(true).get<double>()` throw. This is the behavior of all 3.x versions;
|
||||
[`basic_json_view::get<T>()`](../basic_json_view/get.md) behaves the same.
|
||||
|
||||
!!! note "`std::optional` conversions"
|
||||
|
||||
Prior to version 3.13.0, `#!cpp get<std::optional<T>>()` (and other conversions to `std::optional<T>`) failed to
|
||||
|
||||
@@ -65,12 +65,7 @@ the same index, which is now a different value.
|
||||
|
||||
## Exception safety
|
||||
|
||||
Overload (1) gives the strong guarantee: if an exception is thrown, there are no changes in the JSON value.
|
||||
|
||||
Overloads (2) to (5) give the basic guarantee: if an exception is thrown while the elements or members are copied
|
||||
(for example, `std::bad_alloc`), the JSON value may be partially modified, but it stays a valid array or object.
|
||||
|
||||
The exceptions listed below are thrown before the JSON value is changed.
|
||||
Strong exception safety: if an exception occurs, the original value stays intact.
|
||||
|
||||
## Exceptions
|
||||
|
||||
|
||||
@@ -92,20 +92,12 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
For the **const** version, an object key or array index in `ptr` that does not exist is not reported by an
|
||||
exception, but is undefined behavior (see the notes below). Use [`at`](at.md) for checked access.
|
||||
|
||||
If the **non-const** version throws, the value is left unchanged: values it created for earlier reference tokens
|
||||
of `ptr` are removed again. This also holds for exceptions such as `#!cpp std::bad_alloc` or
|
||||
`#!cpp std::length_error` thrown while padding an array up to a very large index. With custom
|
||||
[template parameters](../../features/types/template_parameters.md), this requires that `erase()` of the object and
|
||||
array types and the move operations of the custom base class do not throw.
|
||||
|
||||
## Complexity
|
||||
|
||||
1. Constant if `idx` is in the range of the array. Otherwise, linear in `idx - size()`.
|
||||
2. Logarithmic in the size of the container.
|
||||
3. Logarithmic in the size of the container.
|
||||
4. Linear in the number of reference tokens of `ptr`; for each of them, logarithmic in the size of the object, or
|
||||
constant for an array. When the non-const version pads an array with `#!json null` values, it is additionally
|
||||
linear in `idx - size()` for that array.
|
||||
4. Logarithmic in the size of the container.
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -153,14 +145,6 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
specified by the JSON Pointer RFC; it is this library's own, intentional disambiguation rule. See also
|
||||
[JSON Pointer](../../features/json_pointer.md).
|
||||
|
||||
!!! warning "Large array indices"
|
||||
|
||||
As the array is filled up with `#!json null` values to the given index, the memory used grows with the index
|
||||
and not with the size of the pointer: on a 64-bit platform, the index `10000000` allocates about 160 MB.
|
||||
The library does not limit the index, so check the array indices of a JSON pointer from untrusted input
|
||||
before using it with the non-const version. See
|
||||
[Parsing untrusted input](../../features/parsing/untrusted_input.md#json-pointers-and-flattened-keys).
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
Overload (4) also accepts a [`json_pointer`](../json_pointer/index.md) whose template argument is a `basic_json`
|
||||
@@ -312,6 +296,4 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
4. Added in version 2.0.0. A missing array index in the const version is guarded by a runtime assertion since
|
||||
version 3.13.0. Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is not
|
||||
a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones. Fixed in version 3.13.0 to leave the value
|
||||
unchanged if the non-const version throws (strong exception safety, as documented); before, values created for earlier
|
||||
reference tokens of `ptr` were kept.
|
||||
a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
|
||||
@@ -63,15 +63,6 @@ called:
|
||||
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
|
||||
the latter case, it is skipped completely, or replaced by `null` if it is the top-level value.
|
||||
|
||||
## Notes
|
||||
|
||||
For a `parse_event_t::key` event, `parsed` holds the decoded object name, with the same guarantees as the `val`
|
||||
parameter of [`json_sax::key`](../json_sax/key.md#notes): all escape sequences are decoded and `\uXXXX` escapes are
|
||||
stored as UTF-8, so different spellings of the same name yield identical strings; the name is validated completely
|
||||
before the callback is called, so a name with an invalid escape sequence or ill-formed UTF-8 causes a parse error and
|
||||
no `key` event; no Unicode normalization or case conversion is applied; and a name that occurs several times in the
|
||||
same object yields one `key` event per occurrence.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: skip an object key while parsing"
|
||||
|
||||
@@ -69,19 +69,6 @@ Duplicate member names inside an operation object are resolved by the parser bef
|
||||
is kept, see [`object_t`](object_t.md#behavior)), so `#!json {"op": "add", "op": "remove", "path": "/a"}` is a
|
||||
`remove` operation.
|
||||
|
||||
The JSON patch may be the JSON value itself (`#!cpp j.patch_inplace(j)`): the operations are then read from a copy of
|
||||
the patch made before the first operation is applied, so the result is the same as that of
|
||||
`#!cpp j = j.patch(j)`, and the exception safety is the same as above.
|
||||
|
||||
If [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) is enabled, the same holds for a JSON patch stored inside the
|
||||
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the parent pointers.
|
||||
|
||||
!!! danger "Undefined behavior"
|
||||
|
||||
Without [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md), the JSON patch must not be a value stored inside the
|
||||
JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`), as the operations may change or destroy it while it is
|
||||
read. Pass a copy (`#!cpp j.patch_inplace(json(j["patch"]))`) or use [`patch`](patch.md) instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Example: apply a JSON patch in place"
|
||||
@@ -135,5 +122,3 @@ JSON value (e.g., `#!cpp j.patch_inplace(j["patch"])`): it is detected via the p
|
||||
- Throws [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) instead of
|
||||
[`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) for a one-character array index that is
|
||||
not a digit (e.g., `/x`) in version 3.13.0, as it already did for longer ones.
|
||||
- Applying a JSON value as patch to itself (or, with `JSON_DIAGNOSTICS`, to a value stored inside it) no longer results
|
||||
in undefined behavior in version 3.13.0.
|
||||
@@ -13,15 +13,9 @@ function never throws an exception for invalid input, and the returned `#!cpp bo
|
||||
is returned.
|
||||
|
||||
The result is the same as that of `BasicJsonType::accept()` by design; the difference is how the check is made. This
|
||||
function runs the document's builder, which checks the text as [`parse()`](parse.md) does, while
|
||||
`BasicJsonType::accept()` runs the SAX parser with a handler that ignores every event. Use this one if you go on to
|
||||
parse the same input with [`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain
|
||||
yes/no answer.
|
||||
|
||||
When both `ignore_comments` and `ignore_trailing_commas` are `#!cpp false` (the default), the builder writes no index.
|
||||
With either option set, it builds the index as [`parse()`](parse.md) does and discards it: the result is the same, only
|
||||
the time differs. (Neither option needs the index; the variant that skips it exists only for the default options to
|
||||
keep the code size down.)
|
||||
function runs the document's index builder (and discards the index), while `BasicJsonType::accept()` runs the SAX
|
||||
parser with a handler that ignores every event. Use this one if you go on to parse the same input with
|
||||
[`parse()`](parse.md) anyway, so that both use the same code; either is fine for a plain yes/no answer.
|
||||
|
||||
## Template parameters
|
||||
|
||||
|
||||
@@ -68,10 +68,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- Throws [`out_of_range.404`](../../home/exceptions.md#jsonexceptionout_of_range404) if `ptr` cannot be resolved
|
||||
because a reference token is used on a primitive value.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`invalid_iterator.214`](../../home/exceptions.md#jsonexceptioninvalid_iterator214) if the view is
|
||||
[null](is_null.md) or [discarded](is_discarded.md), or if it is an empty array or object.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ For the types below, the conversion works directly on the flat index — no `Bas
|
||||
|
||||
- `#!cpp bool`
|
||||
- arithmetic types other than `#!cpp bool` (for a boolean, `#!cpp 0`/`#!cpp 1`, exactly as for
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md#notes), except for `number_integer_t`, `number_unsigned_t` and
|
||||
`number_float_t`, which throw `type_error.302` for a boolean, as `BasicJsonType::get<T>()` does)
|
||||
[`BasicJsonType::get<T>()`](../basic_json/get.md))
|
||||
- `#!cpp std::nullptr_t`
|
||||
- `#!cpp std::basic_string<char, Traits, Alloc>` (including `string_t`) — a copy of the string
|
||||
- [`string_view_t`](index.md#member-types) — **no copy**: the returned view points into the document's
|
||||
@@ -81,13 +80,14 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- For every other `T`: whatever [`materialize().get<T>()`](../basic_json/get.md) throws — typically `type_error.302`,
|
||||
or whatever a user-provided `from_json()` throws.
|
||||
|
||||
The exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four bullets
|
||||
above) carry the context of those of `BasicJsonType`: with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the
|
||||
JSON Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text. An exception
|
||||
thrown while converting through `materialize()` (the last bullet) is thrown by the materialized `BasicJsonType` value
|
||||
instead: its path is relative to the root of the view's subtree, because `materialize()` builds only the subtree
|
||||
(with parent pointers), so it is empty if the failing value is the subtree's root itself.
|
||||
None of the exceptions thrown directly by this function or by a `from_json()` that reads the view (the first four
|
||||
bullets above) carry a
|
||||
[`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no `BasicJsonType` value to point at. An
|
||||
exception thrown while converting through `materialize()` (the last bullet) is different: it is thrown by a real
|
||||
`BasicJsonType` value, so it **does** carry a `JSON_DIAGNOSTICS` path if `BasicJsonType` was built with it enabled.
|
||||
That path is relative to the root of the view's subtree, because `materialize()` builds only the subtree (with parent
|
||||
pointers): it is empty if the failing value is the subtree's root itself, and it never is the path within the whole
|
||||
document.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a string; example:
|
||||
`"type must be string, but is array"`.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -32,24 +32,9 @@ uses, so the result matches it exactly — including, for an object, that a repe
|
||||
last value. The replay is iterative, so it is not limited by the call stack the way a naive recursive conversion
|
||||
would be; the JSON nesting depth is limited only by available memory, as for `BasicJsonType::parse()` itself.
|
||||
|
||||
With [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values get the source
|
||||
positions [`BasicJsonType::parse()`](../basic_json/parse.md) would give them, also for the subtree of a view (the
|
||||
positions are those in the document's whole source text), and exceptions about them name the same bytes. For values of
|
||||
an [editable document](../json_editable_document.md), positions are all-or-nothing, and [`start_pos()`](../basic_json/start_pos.md)
|
||||
and [`end_pos()`](../basic_json/end_pos.md) return `std::string::npos` for
|
||||
|
||||
- a value written by an edit (a copy of an array or object of the document is also new; copies of its scalars keep
|
||||
their positions),
|
||||
- an array or object whose elements were changed (its elements keep theirs), and
|
||||
- an array or object whose last element has no positions (its end is found from that element).
|
||||
|
||||
This differs from a `basic_json` value, whose positions are not updated when it is changed: they stay as they were
|
||||
parsed, even if they no longer match the value (see [`start_pos()`](../basic_json/start_pos.md)).
|
||||
|
||||
Documents [loaded](../basic_json_document/load.md) from an image have the positions of the saved document. Its strings
|
||||
with escapes have positions only if the document was parsed with `JSON_DIAGNOSTIC_POSITIONS`: the position of their
|
||||
opening quote is recorded while parsing, and `save()` and `load()` keep it whether or not they are compiled with the
|
||||
macro.
|
||||
Unlike parsing with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) enabled, the values
|
||||
produced by `materialize()` do not carry source positions. The index has the start offset of every value, but not the
|
||||
end of arrays, objects, and escaped strings, which the positions also need.
|
||||
|
||||
Calling `materialize()` on the same view repeatedly builds a new, independent `BasicJsonType` value each time; it
|
||||
never caches the result.
|
||||
@@ -77,7 +62,8 @@ never caches the result.
|
||||
|
||||
- [`root`](../basic_json_document/root.md) - the view of a document's root value
|
||||
- [`BasicJsonType::parse`](../basic_json/parse.md) - build a `BasicJsonType` value directly from a JSON text
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) - source positions on parsed values (not
|
||||
produced by `materialize()`)
|
||||
- [Zero-copy JSON views](../../features/json_view.md#getting-values-out-without-copying) - when to build a basic_json value from a view
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -22,9 +22,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
Throws [`type_error.302`](../../home/exceptions.md#jsonexceptiontype_error302) if the value is not a number; example:
|
||||
`"type must be number, but is string"`.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) the exception names the JSON Pointer
|
||||
path of the value within the document, and with [`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md)
|
||||
its bytes in the source text (when [`materialize()`](materialize.md) sets them).
|
||||
This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -73,10 +73,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
- [`parse_error.109`](../../home/exceptions.md#jsonexceptionparse_error109) if an array index in `ptr` is not a
|
||||
number.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -61,10 +61,9 @@ Strong guarantee: there are no changes to the view or the document it refers to.
|
||||
`default_value` instead of throwing, exactly as [`BasicJsonType::value`](../basic_json/value.md) catches
|
||||
`out_of_range` and returns `default_value`.
|
||||
|
||||
As for `BasicJsonType`, with [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) these exceptions name the JSON
|
||||
Pointer path of the value within the document, and with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../macros/json_diagnostic_positions.md) its bytes in the source text (when
|
||||
[`materialize()`](materialize.md) sets them).
|
||||
None of these exceptions carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.md) path: the view has no
|
||||
`BasicJsonType` value to point at, so the exception is created without one, even if `BasicJsonType` was built with
|
||||
`JSON_DIAGNOSTICS` enabled.
|
||||
|
||||
## Complexity
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@ json_pointer& operator/=(std::size_t array_idx)
|
||||
## Parameters
|
||||
|
||||
`ptr` (in)
|
||||
: JSON pointer to append; may be the JSON pointer itself (`#!cpp p /= p`), which then appends a copy of its
|
||||
reference tokens
|
||||
: JSON pointer to append
|
||||
|
||||
`token` (in)
|
||||
: reference token to append
|
||||
@@ -70,7 +69,6 @@ json_pointer& operator/=(std::size_t array_idx)
|
||||
|
||||
## Version history
|
||||
|
||||
1. Added in version 3.6.0. Appending a JSON pointer to itself no longer results in undefined behavior in version
|
||||
3.13.0.
|
||||
1. Added in version 3.6.0.
|
||||
2. Added in version 3.6.0. Changed type of `token` to `string_t` in version 3.11.0.
|
||||
3. Added in version 3.6.0.
|
||||
@@ -19,23 +19,6 @@ Whether parsing should proceed.
|
||||
|
||||
It is safe to move the passed object key value.
|
||||
|
||||
When parsing JSON text, `val` is the decoded object name, not its spelling in the input:
|
||||
|
||||
- All escape sequences (`\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, `\t`, `\uXXXX`, and surrogate pairs) are
|
||||
decoded, and `\uXXXX` escapes are stored as UTF-8. Different spellings of the same name, such as `#!json "/"`
|
||||
and `#!json "\/"`, or `#!json "\u00e9"` and `#!json "\u00E9"`, yield identical strings. An escaped `\u0000` yields
|
||||
an embedded NUL byte.
|
||||
- The name is scanned and validated completely before `key` is called. If it contains an invalid escape sequence, an
|
||||
unpaired surrogate, a control character, or ill-formed UTF-8, the parse fails with a
|
||||
[`parse_error`](../../home/exceptions.md#parse-errors), and `key` is not called for it.
|
||||
- No Unicode normalization or case conversion is applied: names that differ in any byte are different strings.
|
||||
- `key` is called before the following `:` is read, so a parse error may still follow for the same member.
|
||||
- A name that occurs several times in the same object is passed once per occurrence.
|
||||
|
||||
For the binary formats, object keys contain no escape sequences. For CBOR, MessagePack, UBJSON, and BSON, `val` holds
|
||||
the bytes stored in the input, and [`sax_parse`](../basic_json/sax_parse.md) passes them unchanged, without checking them
|
||||
for valid UTF-8. BON8 encodes characters itself, so an ill-formed name is a parse error and `key` is not called for it.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -62,7 +45,3 @@ for valid UTF-8. BON8 encodes characters itself, so an ill-formed name is a pars
|
||||
## Version history
|
||||
|
||||
- Added in version 3.2.0.
|
||||
- The behavior described in the notes above (decoded names, validation before `key` is called, no normalization, `key`
|
||||
before the `:` check, one call per duplicate, bytes of binary formats passed unchanged) is the same in all versions
|
||||
from 3.2.0 on, and for the `key` event of a [parser callback](../basic_json/parser_callback_t.md) from 3.0.0 on. BSON
|
||||
is only supported from version 3.4.0, and BON8 only from version 3.13.0.
|
||||
@@ -35,9 +35,6 @@ Note that enabling this macro increases the size of every JSON value by two `std
|
||||
overhead to parsing, copying JSON value objects, and the generation of error messages for exceptions. It also causes
|
||||
these values to be reported in those error messages.
|
||||
|
||||
If [`JSON_DIAGNOSTICS`](json_diagnostics.md) is also set, the message contains the path and the byte range, such as
|
||||
`(/1) (bytes 4-8)`. An exception about the root value has no path and only the range, such as `(bytes 0-4)`.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (position diagnostics are switched off).
|
||||
@@ -116,7 +113,6 @@ When the macro is not defined, the library will define it to its default value.
|
||||
|
||||
- [:simple-cmake: JSON_Diagnostic_Positions](../../integration/cmake.md#json_diagnostic_positions) - CMake option to control the macro
|
||||
- [`JSON_DIAGNOSTICS`](json_diagnostics.md) - macro to control extended diagnostics
|
||||
- [`materialize()`](../basic_json_view/materialize.md) - values of a JSON view, with the same positions
|
||||
- [Parsing and Exceptions](../../features/parsing/parse_exceptions.md) - handling parse errors and locating them in the input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -78,9 +78,8 @@ flowchart LR
|
||||
- `NaN` and [discarded](../api/basic_json/is_discarded.md) values are not equal to themselves, so the invariant
|
||||
cannot hold for them. `diff` emits a `replace` operation for such a value, and a `NaN` in a patch is serialized
|
||||
as `#!json null`.
|
||||
- Numbers that compare equal produce no operation even if their types differ or if one is zero and the other is
|
||||
negative zero: `diff(1, 1.0)`, `diff(1u, 1)`, and `diff(0.0, -0.0)` return `#!json []`, so the patched value
|
||||
keeps the number type and sign of *source*.
|
||||
- Numbers that compare equal produce no operation even if their types differ: `diff(1, 1.0)`, `diff(1u, 1)`, and
|
||||
`diff(0.0, -0.0)` return `#!json []`, so the patched value keeps the number type of *source*.
|
||||
- A [binary value](binary_values.md) in a patch is serialized as an object
|
||||
(`#!json {"bytes":[...],"subtype":null}`), so a patch that was dumped and parsed again no longer holds a binary
|
||||
value.
|
||||
|
||||
@@ -134,21 +134,9 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
- **Macros.** [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) and
|
||||
[`JSON_NOEXCEPTION`](../api/macros/json_noexception.md)/[`JSON_THROW_USER`](../api/macros/json_throw_user.md)
|
||||
behave the same way they do for `<nlohmann/json.hpp>`. These are the macros that change what the parser accepts or
|
||||
how it reports errors. Two more macros add context to the exceptions of views, as they do for those of
|
||||
`#!cpp basic_json`:
|
||||
|
||||
- With [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md), an exception names the JSON Pointer path of the
|
||||
value within the document.
|
||||
- With [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), an exception names the bytes of
|
||||
the value in the source text.
|
||||
|
||||
A view has neither parent pointers nor stored positions, so both are found from the document's index when the
|
||||
exception is thrown. This takes time linear in the size of the document. One exception has neither:
|
||||
`key()` of an iterator over an array or a primitive value
|
||||
([`invalid_iterator.207`](../home/exceptions.md#jsonexceptioninvalid_iterator207)), because a view's iterator does
|
||||
not know the value it iterates over.
|
||||
|
||||
The other macros only affect the values `materialize()` produces, through `BasicJsonType`.
|
||||
how it reports errors. The others either do not apply ([`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md)
|
||||
needs parent pointers in `basic_json` values) or only affect the values `materialize()` produces, through
|
||||
`BasicJsonType`.
|
||||
|
||||
## What is different
|
||||
|
||||
@@ -161,12 +149,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
end must still fit.
|
||||
- **A stream is always read to its end.** There is no partial/streaming read of an `#!cpp std::istream`: the stream
|
||||
is read to its end into a buffer the document owns, and parsing starts after that.
|
||||
- **Fewer source positions after edits.** With
|
||||
- **No source positions on `materialize()`.** Even with
|
||||
[`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) enabled,
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) sets the same positions as `#!cpp basic_json::parse()`.
|
||||
In an [editable document](../api/json_editable_document.md), a value written by an edit has none, and neither has
|
||||
an array or object whose elements changed or whose last element has none; a `basic_json` value keeps its positions
|
||||
when it is changed. See [`materialize()`](../api/basic_json_view/materialize.md#notes).
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) does not set them. The index has the start offset of every
|
||||
value, but not the end of arrays, objects, and escaped strings, which the positions also need.
|
||||
- **Objects iterate in document order.** [`begin()`](../api/basic_json_view/begin.md)/
|
||||
[`end()`](../api/basic_json_view/end.md) and [`items()`](../api/basic_json_view/items.md) visit an object's
|
||||
members in the order they appear in the source text. `basic_json`'s default `object_t` is a `std::map`, which
|
||||
@@ -179,6 +165,10 @@ document: `#!cpp auto v = json_document::parse(text).root();` does not compile.
|
||||
[`at`](../api/basic_json_view/at.md) throws for every missing value.
|
||||
- **Duplicate keys: lookups find the first member, `parse()` keeps the last.** See [Duplicate keys](#duplicate-keys)
|
||||
below.
|
||||
- **No [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) path.** Exceptions thrown by `basic_json_view`'s own
|
||||
element access and lookup functions never carry the JSON Pointer path `JSON_DIAGNOSTICS` would otherwise add: the
|
||||
view has no `basic_json` value to point at, so the exception is created without one, regardless of how
|
||||
`BasicJsonType` was built.
|
||||
- **Ordering comparisons are not provided** by `basic_json_view` — there is no `#!cpp operator<`.
|
||||
[`operator==`](../api/basic_json_view/operator_eq.md) and [`operator!=`](../api/basic_json_view/operator_ne.md) are
|
||||
provided, though: two views, or a view and a `BasicJsonType` value, compare equal exactly when
|
||||
@@ -261,34 +251,6 @@ Two conversions never copy at all:
|
||||
|
||||
Both results are only valid as long as the view — and, for a string with no escapes, the borrowed source text — is.
|
||||
|
||||
### `string_view` members
|
||||
|
||||
A struct whose member is a `#!cpp std::string_view` (C++17) can be decoded from a view with the
|
||||
`NLOHMANN_DEFINE_TYPE_*` macros, and the member is not copied either: for a string without escape sequences it points
|
||||
into the document's source text, and for a string with escape sequences into the document's buffer of decoded strings.
|
||||
The member is valid as long as the document is alive and, for a document that
|
||||
[borrows its source text](#ownership-and-lifetime) (as [`parse()`](../api/basic_json_document/parse.md) does for
|
||||
an lvalue `#!cpp std::string`, a `#!cpp std::string_view`, or a C string), as long as that text is.
|
||||
|
||||
```cpp
|
||||
struct record
|
||||
{
|
||||
std::string_view name;
|
||||
int count;
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(record, name, count)
|
||||
};
|
||||
|
||||
const std::string text = R"({"name": "widget", "count": 3})";
|
||||
const json_document doc = json_document::parse(text); // borrows text
|
||||
const record r = doc.root().get<record>(); // r.name points into text, valid while text and doc are
|
||||
```
|
||||
|
||||
!!! warning "Decoding into a `string_view` from a `json` value dangles at once"
|
||||
|
||||
The same struct also compiles with `#!cpp json::parse(text).get<record>()`, but there `name` points into the
|
||||
temporary `json` value, which is destroyed at the end of the statement. Use a named
|
||||
[`json_document`](../api/json_document.md), as above, to decode such a struct.
|
||||
|
||||
## Writing generic code
|
||||
|
||||
A view's type names its `BasicJsonType` and its [`view_mode`](../api/view_mode.md):
|
||||
@@ -365,10 +327,14 @@ Its body may only use what views and `basic_json` have in common: the type check
|
||||
a view does not have; such a type is converted through `materialize()`. A `from_json()` for `json` only is
|
||||
converted through `materialize()` as well.
|
||||
|
||||
One thing differs when a `from_json()` reads the view: members looked up by name (`at()`, `value()`, ... and thus
|
||||
the macros) find the **first** member with a repeated key, see [Duplicate keys](#duplicate-keys);
|
||||
`#!cpp materialize().get<T>()` would see the last one. Containers built from an object, such as `#!cpp std::map`,
|
||||
keep the **last** value either way, as [`parse()`](../api/basic_json/parse.md) does.
|
||||
Two things differ when a `from_json()` reads the view:
|
||||
|
||||
- **Duplicate keys.** Members looked up by name (`at()`, `value()`, ... and thus the macros) find the **first** member
|
||||
with a repeated key, see [Duplicate keys](#duplicate-keys); `#!cpp materialize().get<T>()` would see the last one.
|
||||
Containers built from an object, such as `#!cpp std::map`, keep the **last** value either way, as
|
||||
[`parse()`](../api/basic_json/parse.md) does.
|
||||
- **[`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md).** Exceptions thrown by the view carry no path to the
|
||||
value.
|
||||
|
||||
`to_json()` is unaffected: it writes `basic_json` values, never views.
|
||||
|
||||
|
||||
@@ -64,9 +64,6 @@ bool parse_error(std::size_t position, const std::string& last_token, const json
|
||||
|
||||
The return value of each function determines whether parsing should proceed.
|
||||
|
||||
Object keys are passed to `key` decoded and validated: escape sequences are already resolved, so different spellings of
|
||||
the same name yield the same string. See [`key`](../../api/json_sax/key.md#notes) for the exact guarantees.
|
||||
|
||||
## Stepping through the events
|
||||
|
||||
The events for a small input, step by step, and what two different handlers do with them. Press *Play* or step with *Next*:
|
||||
|
||||
@@ -130,17 +130,6 @@ parameters of [`parse()`](../../api/basic_json/parse.md) or [`accept()`](../../a
|
||||
enable either for input whose conformance you cannot otherwise control, since interoperability with strictly
|
||||
conforming JSON consumers is exactly what the default rejects.
|
||||
|
||||
## JSON Pointers and flattened keys
|
||||
|
||||
A [JSON Pointer](../json_pointer.md) can come from untrusted input as well, for instance as a path in a request or as a
|
||||
key of a flattened object. The non-const [`operator[]`](../../api/basic_json/operator%5B%5D.md) with a JSON Pointer and
|
||||
[`unflatten`](../../api/basic_json/unflatten.md) create missing values, and when an array index lies beyond the end of
|
||||
an array, they fill the array with `#!json null` values up to that index. The memory used therefore grows with the
|
||||
index, not with the size of the pointer: on a 64-bit platform, the index `10000000` allocates about 160 MB, the index
|
||||
`100000000` about 1.6 GB. The library does not limit the index. For pointers from untrusted input, check the array
|
||||
indices (or the pointer as a whole) before using them, or use [`at`](../../api/basic_json/at.md) or
|
||||
[`contains`](../../api/basic_json/contains.md), which never add values.
|
||||
|
||||
## Checklist
|
||||
|
||||
- Wrap parsing in a `#!cpp try`/`#!cpp catch` block, or use `allow_exceptions=false`/`accept()` if your environment
|
||||
@@ -156,8 +145,6 @@ indices (or the pointer as a whole) before using them, or use [`at`](../../api/b
|
||||
`JSON_STRICT_NUL_HANDLING`.
|
||||
- Decide whether duplicate object keys should be an error for your application, and add a callback if so.
|
||||
- Do not enable `ignore_comments` or `ignore_trailing_commas` for input that must be strictly conforming JSON.
|
||||
- Check the array indices of untrusted JSON Pointers and flattened keys before passing them to the non-const
|
||||
`operator[]` or `unflatten()`, which fill arrays with `#!json null` values up to the index.
|
||||
|
||||
For the broader design rationale and how it is tested (fuzzing, sanitizers, static analysis), see the
|
||||
[assurance case](../../community/assurance_case.md) and [quality assurance](../../community/quality_assurance.md). To
|
||||
|
||||
@@ -406,7 +406,6 @@ using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;
|
||||
| `#!cpp operator<<(std::ostream&, const json_pointer&)` | streamability to `#!cpp std::ostream` |
|
||||
| [`to_string`](../../api/basic_json/to_string.md) | conversion of `StringType` to `#!cpp std::string` (the function returns a `#!cpp std::string`) |
|
||||
| exception messages | `data()` and `size()`, or `begin()` and `end()` |
|
||||
| [`basic_json_view::dump`](../../api/basic_json_view/dump.md) | `reserve(n)` and `resize(n)` (one argument); `capacity()` is **not** required |
|
||||
|
||||
### Compatible types
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ packet-beta
|
||||
| 2-3 | `extra` | `uint16_t` | numbers: the number of integer digits (low byte) and fraction digits (high byte), 255 for more; objects: the number of their key hash index (1-based), or 0; otherwise 0 |
|
||||
| 4-7 | `off` | `uint32_t` | where the value starts: the first byte after a string's opening quote (or its position in the buffer of decoded strings), the first byte of a number or literal, the bracket of an array or object |
|
||||
| 8-11 | `len` | `uint32_t` | strings: the length after decoding; floats and literals: the length of the token; arrays and objects: the number of elements |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself; strings with escapes: the position of the opening quote in the source plus one if the document was parsed with [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md), or 0 if it was not; all other values: 0 |
|
||||
| 12-15 | `next` | `uint32_t` | arrays and objects: the number of nodes of the subtree, including the node itself |
|
||||
|
||||
<p class="nj-table-caption" markdown>Fields of a node, by their byte offset within the 16-byte node. Integers use bytes 8-15 differently, see below.</p>
|
||||
|
||||
@@ -218,12 +218,6 @@ packet-beta
|
||||
step from element to element this way and skip whole subtrees in constant time.
|
||||
- **Offsets** are 32 bits wide, so a document is limited to 4294967279 bytes, 4 GiB minus 16 bytes (a margin below
|
||||
2^32 for positions one scanner step past the end of the text; `out_of_range.416`).
|
||||
- **Source positions** ([`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md)) are derived by
|
||||
[`materialize()`](../api/basic_json_view/materialize.md) and cost almost nothing while parsing: the only work is
|
||||
storing the offset of the opening quote of a string with escapes in its `next`, which is otherwise unused. A
|
||||
scalar's range follows from `off` and its length, a string with escapes is scanned from its opening quote (`next`) to the closing one,
|
||||
and the end of an array or object is found by scanning from the end of its last element over whitespace, comments,
|
||||
and a trailing comma. `materialize()` hands these positions to the SAX builder of `parse()` in place of its lexer.
|
||||
- **Large objects** (128 members or more) get a hash index for their keys after parsing
|
||||
([`detail/view/object_index.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/view/object_index.hpp)):
|
||||
an open-addressing table whose slots hold the distance from the object's node to a key's node, so that a lookup does
|
||||
@@ -283,7 +277,7 @@ Images ([`save`](../api/basic_json_document/save.md) and [`load`](../api/basic_j
|
||||
the nodes as they are: a 64-byte header (the magic bytes `NJVI`, a format version, the sizes, and reserved bytes that
|
||||
must be zero), the nodes, the text, and the decoded strings. An edited document is first written in document order, as
|
||||
the parser would have written it (without links), and the numbers of the hash indexes are cleared, since `load`
|
||||
rebuilds the indexes. The flag *new* is kept, as these values have no position in the text. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
rebuilds the indexes. So a change of the node layout is a change of the image format: it must raise `image_version`,
|
||||
and `load` then rejects images of other versions (`parse_error.116`) instead of misreading them.
|
||||
|
||||
## Input adapters
|
||||
|
||||
@@ -10,13 +10,6 @@ at its root, a [Natvis](https://learn.microsoft.com/en-us/visualstudio/debugger/
|
||||
file that gives `json`/`ordered_json` values a friendly, key/value debugger view instead of showing raw internal
|
||||
fields, when debugging with the MSVC debug engine (`cppvsdbg`) in Visual Studio or VS Code.
|
||||
|
||||
The file also has visualizers for `json_document` and `json_view` (and their `ordered_json`/editable variants). They
|
||||
show the value of the document or view, and its elements as an expandable list. Because a document is only a flat index
|
||||
into the source text, the strings of the direct elements are shown, but the elements of nested arrays and objects are
|
||||
shown as index nodes (their kind, size, and numbers; strings only by length). Arrays and objects that have been edited
|
||||
in a `json_editable_document` are not expanded. The visualizers read the internal layout of the MSVC STL (`std::array`,
|
||||
`std::unique_ptr`).
|
||||
|
||||
The checked-in visualizers cover namespace variants with zero, one, or two ABI tags. Configurations with three or more
|
||||
ABI tags are not covered and may show raw internal fields instead. To generate a Natvis file for such a configuration:
|
||||
|
||||
@@ -36,7 +29,7 @@ ABI tags are not covered and may show raw internal fields instead. To generate a
|
||||
|
||||
4. Use the generated file instead of `nlohmann_json.natvis`.
|
||||
|
||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 11
|
||||
`--abi-tags` can be repeated for several configurations, and `--all` generates every combination (about 60 MB for 12
|
||||
ABI tags, doubling with each further tag). See
|
||||
[`tools/generate_natvis`](https://github.com/nlohmann/json/tree/develop/tools/generate_natvis) for all options.
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ The class contains a port of the shortest double-to-decimal conversion of [Żmij
|
||||
|
||||
The class contains a copy of [Hedley](https://nemequ.github.io/hedley/) from Evan Nemerson which is licensed as [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, its digit comparison for long numbers, and its parsing of eight digits at a time from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
The class contains an adapted version of the Eisel-Lemire algorithm, its table of powers of five, and its digit comparison for long numbers from [fast_float](https://github.com/fastfloat/fast_float) by Daniel Lemire and contributors, which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here), the Apache 2.0 License, and the Boost Software License. Copyright © 2021 The fast_float authors
|
||||
|
||||
The view's parser (`<nlohmann/json_view.hpp>`) contains techniques and code adapted from [yyjson](https://github.com/ibireme/yyjson) by YaoYuan, which is licensed under the [MIT License](https://opensource.org/licenses/MIT) (see above): table-driven decoding of `\u` escapes and fixed-offset unrolled checks.
|
||||
|
||||
The class's lexer and the view's parser (`<nlohmann/json_view.hpp>`) validate non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
The view's parser (`<nlohmann/json_view.hpp>`) validates non-ASCII strings with the vector UTF-8 check of [simdjson](https://github.com/simdjson/simdjson) by Daniel Lemire, Geoff Langdale, John Keiser, and contributors (its "lookup4" algorithm and tables, after J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per Byte", 2021), which is available under the [MIT License](https://opensource.org/licenses/MIT) (used here) and the Apache 2.0 License. Copyright © 2018-2025 The simdjson authors
|
||||
@@ -1,8 +1,8 @@
|
||||
wheel==0.48.0
|
||||
|
||||
mkdocs==1.6.1 # documentation framework
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.2 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.8.0 # theme for mkdocs
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.0 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.7.7 # theme for mkdocs
|
||||
mkdocs-material-extensions==1.3.1 # extensions
|
||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||
|
||||
@@ -91,7 +91,7 @@ const typename BasicJsonType::binary_t& binary_value(const BasicJsonType& j)
|
||||
}
|
||||
|
||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/) // LCOV_EXCL_LINE
|
||||
typename View::basic_json_type::binary_t binary_value(const View& /*unused*/)
|
||||
{
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@
|
||||
#include <type_traits> // conditional
|
||||
|
||||
|
||||
// The shortest-digit conversion of doubles (see zmij.hpp) and the writing of
|
||||
// its digits, including the conversion of the digits to text by Xiang JunBo
|
||||
// and the SIMD instruction sequence by Dougall Johnson, are ported from Zmij
|
||||
// by Victor Zverovich (https://github.com/vitaut/zmij, MIT license).
|
||||
//
|
||||
// SSE2 (every x86-64 CPU) and NEON (every 64-bit Arm CPU) convert the 16
|
||||
// digits of a double at once (unless JSON_NO_SIMD selects the portable code)
|
||||
#if !defined(JSON_NO_SIMD) && (defined(__x86_64__) || (defined(_M_X64) && !defined(_M_ARM64EC)))
|
||||
|
||||
@@ -198,19 +198,18 @@ class exception : public std::exception
|
||||
// all other parent types (primitives) contribute no token
|
||||
}
|
||||
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string prefix;
|
||||
if (!tokens.empty())
|
||||
if (tokens.empty())
|
||||
{
|
||||
const auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
prefix = concat('(', str, ") ");
|
||||
return "";
|
||||
}
|
||||
|
||||
return concat(prefix, get_byte_positions(leaf_element));
|
||||
auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},
|
||||
[](const std::string & a, const std::string & b)
|
||||
{
|
||||
return concat(a, '/', detail::escape(b));
|
||||
});
|
||||
|
||||
return concat('(', str, ") ", get_byte_positions(leaf_element));
|
||||
#else
|
||||
return get_byte_positions(leaf_element);
|
||||
#endif
|
||||
@@ -223,8 +222,7 @@ class exception : public std::exception
|
||||
template<typename BasicJsonType>
|
||||
static std::string get_byte_positions(const BasicJsonType* leaf_element)
|
||||
{
|
||||
// a value-initialized iterator has no value (invalid_iterator.212)
|
||||
if (leaf_element != nullptr && leaf_element->start_pos() != std::string::npos && leaf_element->end_pos() != std::string::npos)
|
||||
if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))
|
||||
{
|
||||
return concat("(bytes ", std::to_string(leaf_element->start_pos()), "-", std::to_string(leaf_element->end_pos()), ") ");
|
||||
}
|
||||
@@ -433,28 +431,6 @@ std::nullptr_t exception_context(const View& /*unused*/) noexcept
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
namespace view
|
||||
{
|
||||
// the path and bytes of the value of a view (defined in <nlohmann/detail/view/errors.hpp>)
|
||||
template<typename View>
|
||||
std::string diagnostics_of(const View& v);
|
||||
} // namespace view
|
||||
|
||||
// what goes in front of the message of an exception about j: nothing for a
|
||||
// basic_json value (exception_context() passes the value itself), the context
|
||||
// basic_json would add (JSON_DIAGNOSTICS, JSON_DIAGNOSTIC_POSITIONS) for a view
|
||||
template<typename BasicJsonType, enable_if_t<is_basic_json<BasicJsonType>::value, int> = 0>
|
||||
const char* exception_prefix(const BasicJsonType& /*unused*/) noexcept
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
template<typename View, enable_if_t<is_json_view<View>::value, int> = 0>
|
||||
std::string exception_prefix(const View& v)
|
||||
{
|
||||
return view::diagnostics_of(v);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because @a j does not have the type a conversion expects
|
||||
@param[in] expected the expected type(s), e.g. "array" or "binary or array"
|
||||
@@ -466,7 +442,7 @@ JSON_HEDLEY_NO_RETURN JSON_HEDLEY_NEVER_INLINE void throw_type_must_be(const cha
|
||||
{
|
||||
static_cast<void>(expected); // unused when JSON_NOEXCEPTION is defined
|
||||
static_cast<void>(j);
|
||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat(exception_prefix(j), "type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||
JSON_THROW(type_error::create(exception_id::type_mismatch, concat("type must be ", expected, ", but is ", j.type_name()), exception_context(j)));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <bit> //byteswap
|
||||
#endif
|
||||
|
||||
#include <nlohmann/detail/bit_ops.hpp>
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||
#include <nlohmann/detail/input/json_sax.hpp>
|
||||
@@ -466,19 +465,15 @@ class binary_reader
|
||||
}
|
||||
|
||||
const std::size_t element_type_parse_position = chars_read;
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on: they are skipped, and only
|
||||
// checked if error_handler_t::strict must reject ill-formed UTF-8
|
||||
const bool is_object = container_stack.back().is_object;
|
||||
key.clear();
|
||||
if (JSON_HEDLEY_UNLIKELY(!(is_object || error_handler != error_handler_t::strict
|
||||
? get_bson_cstr(key, is_object)
|
||||
: check_bson_cstr(key, std::integral_constant<bool, bulk_scan> {}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_object && !sax->key(key))
|
||||
// an array's elements are named "0", "1", ... in the wire format,
|
||||
// and those names are not passed on
|
||||
if (container_stack.back().is_object && !sax->key(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -494,18 +489,14 @@ class binary_reader
|
||||
@brief Parses a C-style string from the BSON input.
|
||||
@param[in,out] result A reference to the string variable where the read
|
||||
string is to be stored.
|
||||
@param[in] store whether the characters are needed; if not (the names of
|
||||
an array's elements), they are skipped and @a result may
|
||||
be left unchanged, and they are not checked against
|
||||
@ref error_handler (@ref check_bson_cstr checks them)
|
||||
@return `true` if the \\x00-byte indicating the end of the string was
|
||||
encountered before the EOF; false` indicates an unexpected EOF.
|
||||
*/
|
||||
bool get_bson_cstr(string_t& result, const bool store = true)
|
||||
bool get_bson_cstr(string_t& result)
|
||||
{
|
||||
if (get_bson_cstr_bulk(result, store, std::integral_constant<bool, bulk_scan> {}))
|
||||
if (get_bson_cstr_bulk(result, std::integral_constant<bool, bulk_scan> {}))
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
|
||||
auto out = std::back_inserter(result);
|
||||
@@ -518,7 +509,7 @@ class binary_reader
|
||||
}
|
||||
if (current == 0x00)
|
||||
{
|
||||
return !store || check_string_utf8(result, "key");
|
||||
return check_string_utf8(result, "key");
|
||||
}
|
||||
*out++ = static_cast<typename string_t::value_type>(current);
|
||||
}
|
||||
@@ -528,11 +519,10 @@ class binary_reader
|
||||
@brief read a C-style string from contiguous input in one step
|
||||
|
||||
@param[in,out] result the string to append to
|
||||
@param[in] store whether to append the characters to @a result
|
||||
@return whether the string was read; if the input has no \\x00-byte, nothing
|
||||
is read, and @ref get_bson_cstr reports the end of the input
|
||||
*/
|
||||
bool get_bson_cstr_bulk(string_t& result, const bool store, std::true_type /*bulk*/)
|
||||
bool get_bson_cstr_bulk(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
@@ -540,15 +530,18 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
// a plain loop rather than std::memchr: most keys are short (array
|
||||
// indices are keys, too), and the call would cost more than it saves
|
||||
std::size_t length = 0;
|
||||
while (length < remaining && data[length] != 0x00)
|
||||
{
|
||||
++length;
|
||||
}
|
||||
if (length == remaining)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (store)
|
||||
{
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
}
|
||||
result.append(reinterpret_cast<const typename string_t::value_type*>(data), length);
|
||||
// consume the string and its \x00-byte, as the byte-wise path does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
@@ -556,44 +549,8 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip a C-style string that error_handler_t::strict must check
|
||||
|
||||
The names of an array's elements are not passed on. On contiguous input,
|
||||
a well-formed name is checked in place and skipped without being copied.
|
||||
Otherwise (no \\x00-byte in the input that can be scanned, or ill-formed
|
||||
UTF-8), it is read by @ref get_bson_cstr, which reports the error.
|
||||
|
||||
@param[in,out] result an empty string, which may receive the name
|
||||
@return whether the name was read and is well-formed
|
||||
*/
|
||||
bool check_bson_cstr(string_t& result, std::true_type /*bulk*/)
|
||||
{
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining != 0)
|
||||
{
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const std::size_t length = find_zero_byte(data, remaining);
|
||||
if (length != remaining && JSON_HEDLEY_LIKELY(valid_utf8_string(data, length)))
|
||||
{
|
||||
// consume the string and its \x00-byte, as get_bson_cstr does
|
||||
ia.bulk_skip(length + 1);
|
||||
chars_read += length + 1;
|
||||
current = 0x00;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: the name is read and checked byte by byte
|
||||
bool check_bson_cstr(string_t& result, std::false_type /*bulk*/)
|
||||
{
|
||||
return get_bson_cstr(result);
|
||||
}
|
||||
|
||||
/// input that is not contiguous: C-style strings are read byte by byte
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, const bool /*store*/, std::false_type /*bulk*/) const noexcept
|
||||
bool get_bson_cstr_bulk(string_t& /*result*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -784,13 +741,11 @@ class binary_reader
|
||||
bool get_cbor_negative_integer()
|
||||
{
|
||||
NumberType number{};
|
||||
return get_number(number) && emit_cbor_negative_integer(number);
|
||||
}
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_number(number)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// pass the CBOR negative integer -1 - @a number to the SAX parser
|
||||
template<typename NumberType>
|
||||
bool emit_cbor_negative_integer(const NumberType number)
|
||||
{
|
||||
// the value is -1 - number, which fits into number_integer_t
|
||||
// whenever number does; the outer cast undoes the integral promotion
|
||||
// for number_integer_t types narrower than int
|
||||
@@ -1015,7 +970,7 @@ class binary_reader
|
||||
case 0x95:
|
||||
case 0x96:
|
||||
case 0x97:
|
||||
return enter_cbor_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu));
|
||||
|
||||
case 0x98: // array (one-byte uint8_t for n follows)
|
||||
case 0x99: // array (two-byte uint16_t for n follow)
|
||||
@@ -1024,7 +979,7 @@ class binary_reader
|
||||
{
|
||||
std::uint64_t len{};
|
||||
std::size_t size{};
|
||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size);
|
||||
return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_array(size);
|
||||
}
|
||||
|
||||
case 0x9F: // array (indefinite length)
|
||||
@@ -1178,10 +1133,16 @@ class binary_reader
|
||||
return get_half_float(false);
|
||||
|
||||
case 0xFA: // Single-Precision Float (four-byte IEEE 754)
|
||||
return get_cbor_float<float>();
|
||||
{
|
||||
float number{};
|
||||
return get_number(number) && emit_float(number);
|
||||
}
|
||||
|
||||
case 0xFB: // Double-Precision Float (eight-byte IEEE 754)
|
||||
return get_cbor_float<double>();
|
||||
{
|
||||
double number{};
|
||||
return get_number(number) && emit_float(number);
|
||||
}
|
||||
|
||||
default: // anything else (0xFF is handled inside the other types)
|
||||
{
|
||||
@@ -1190,14 +1151,6 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/// read a single- or double-precision float
|
||||
template<typename NumberType>
|
||||
bool get_cbor_float()
|
||||
{
|
||||
NumberType number{};
|
||||
return get_number(number) && emit_float(number);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief reports a nested indefinite-length CBOR string or byte array
|
||||
@param[in] type_name name of the rejected string type
|
||||
@@ -1723,159 +1676,6 @@ class binary_reader
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a big-endian number of type @a NumberType at @a data
|
||||
*/
|
||||
template<typename NumberType>
|
||||
NumberType load_big_endian(const unsigned char* data) const noexcept
|
||||
{
|
||||
NumberType result{};
|
||||
std::memcpy(&result, data, sizeof(NumberType));
|
||||
if (is_little_endian)
|
||||
{
|
||||
byte_swap(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read the integers, floats, booleans, and nulls that follow in a
|
||||
definite-length array, without the per-element bookkeeping of
|
||||
@ref parse_cbor_internal
|
||||
|
||||
Stops at the end of the array, at any other kind of value, and when fewer
|
||||
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||
those to @ref parse_cbor_value, which reads them the same way.
|
||||
|
||||
Out of line, so that @ref parse_cbor_value stays small enough to be inlined.
|
||||
|
||||
The array is the one on top of @ref container_stack; its count of
|
||||
elements not yet read is updated.
|
||||
|
||||
@return whether the SAX parser accepted the values
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool parse_cbor_scalar_run(std::true_type /*bulk*/)
|
||||
{
|
||||
// nothing pushes onto the stack while the run reads scalars
|
||||
std::size_t& remaining = container_stack.back().remaining;
|
||||
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||
{
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const unsigned char head = data[0];
|
||||
std::size_t size = 0;
|
||||
bool accepted = false;
|
||||
switch (head)
|
||||
{
|
||||
case 0x18:
|
||||
size = 2;
|
||||
accepted = emit_unsigned(data[1]);
|
||||
break;
|
||||
case 0x19:
|
||||
size = 3;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||
break;
|
||||
case 0x1A:
|
||||
size = 5;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||
break;
|
||||
case 0x1B:
|
||||
size = 9;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||
break;
|
||||
case 0x38:
|
||||
size = 2;
|
||||
accepted = emit_cbor_negative_integer(data[1]);
|
||||
break;
|
||||
case 0x39:
|
||||
size = 3;
|
||||
accepted = emit_cbor_negative_integer(load_big_endian<std::uint16_t>(data + 1));
|
||||
break;
|
||||
case 0x3A:
|
||||
size = 5;
|
||||
accepted = emit_cbor_negative_integer(load_big_endian<std::uint32_t>(data + 1));
|
||||
break;
|
||||
case 0x3B:
|
||||
size = 9;
|
||||
accepted = emit_cbor_negative_integer(load_big_endian<std::uint64_t>(data + 1));
|
||||
break;
|
||||
case 0xF4:
|
||||
size = 1;
|
||||
accepted = sax->boolean(false);
|
||||
break;
|
||||
case 0xF5:
|
||||
size = 1;
|
||||
accepted = sax->boolean(true);
|
||||
break;
|
||||
case 0xF6:
|
||||
size = 1;
|
||||
accepted = sax->null();
|
||||
break;
|
||||
case 0xFA:
|
||||
size = 5;
|
||||
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||
break;
|
||||
case 0xFB:
|
||||
size = 9;
|
||||
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||
break;
|
||||
default:
|
||||
if (head <= 0x17)
|
||||
{
|
||||
size = 1;
|
||||
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||
}
|
||||
else if (head >= 0x20 && head <= 0x37)
|
||||
{
|
||||
size = 1;
|
||||
accepted = sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - head));
|
||||
}
|
||||
else
|
||||
{
|
||||
// any other value is read by parse_cbor_value
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// as if read by get() and get_number(): current is the head byte
|
||||
ia.bulk_skip(size);
|
||||
chars_read += size;
|
||||
current = head;
|
||||
--remaining;
|
||||
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief open a definite-length array and read the scalars it begins with
|
||||
(see @ref parse_cbor_scalar_run)
|
||||
|
||||
Only the start of an array is tried, which covers arrays of numbers
|
||||
(e.g. coordinates) but costs arrays of other values only one call, not one
|
||||
per element. An array of one element is not tried at all: that element is
|
||||
mostly an object or an array (`[[...]]`, the single ring of a GeoJSON
|
||||
polygon), for which the call would find no scalar, while a single scalar,
|
||||
which the run would read faster, is rare. The benchmarks
|
||||
`FromBinaryShape/nested` and `FromBinaryShape/singles` show both sides.
|
||||
|
||||
@param[in] len the number of elements
|
||||
@return whether the SAX parser accepted the array and the values read
|
||||
*/
|
||||
bool enter_cbor_array(const std::size_t len)
|
||||
{
|
||||
return enter_array(len) && (len <= 1 || parse_cbor_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
}
|
||||
|
||||
/// input that is not contiguous: every value is read by parse_cbor_value
|
||||
bool parse_cbor_scalar_run(std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a CBOR value and everything nested inside it
|
||||
|
||||
@@ -1905,7 +1705,6 @@ class binary_reader
|
||||
// pop_back() below, which destroys the container_stack element
|
||||
// it would otherwise alias
|
||||
const container_frame top = container_stack.back();
|
||||
const bool is_object = top.is_object;
|
||||
bool at_end = false;
|
||||
|
||||
if (top.remaining != npos)
|
||||
@@ -1917,7 +1716,7 @@ class binary_reader
|
||||
{
|
||||
// claim the element about to be read
|
||||
--container_stack.back().remaining;
|
||||
if (is_object)
|
||||
if (top.is_object)
|
||||
{
|
||||
get();
|
||||
}
|
||||
@@ -1930,7 +1729,7 @@ class binary_reader
|
||||
// Testing for it consumes a byte, which is the first byte
|
||||
// of the next element when it is not one.
|
||||
at_end = (get() == 0xFF);
|
||||
fetch = is_object;
|
||||
fetch = top.is_object;
|
||||
}
|
||||
|
||||
if (at_end)
|
||||
@@ -1947,7 +1746,7 @@ class binary_reader
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_object)
|
||||
if (top.is_object)
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!skip_cbor_key_tags(tag_handler)))
|
||||
{
|
||||
@@ -2172,7 +1971,7 @@ class binary_reader
|
||||
case 0x9D:
|
||||
case 0x9E:
|
||||
case 0x9F:
|
||||
return enter_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
|
||||
return enter_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));
|
||||
|
||||
// fixstr
|
||||
case 0xA0:
|
||||
@@ -2303,13 +2102,13 @@ class binary_reader
|
||||
case 0xDC: // array 16
|
||||
{
|
||||
std::uint16_t len{};
|
||||
return get_number(len) && enter_msgpack_array(static_cast<std::size_t>(len));
|
||||
return get_number(len) && enter_array(static_cast<std::size_t>(len));
|
||||
}
|
||||
|
||||
case 0xDD: // array 32
|
||||
{
|
||||
std::uint32_t len{};
|
||||
return get_number(len) && enter_msgpack_array(conditional_static_cast<std::size_t>(len));
|
||||
return get_number(len) && enter_array(conditional_static_cast<std::size_t>(len));
|
||||
}
|
||||
|
||||
case 0xDE: // map 16
|
||||
@@ -2651,141 +2450,6 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read the integers, floats, booleans, and nils that follow in a
|
||||
MessagePack array, without the per-element bookkeeping of
|
||||
@ref parse_msgpack_internal
|
||||
|
||||
Stops at the end of the array, at any other kind of value, and when fewer
|
||||
than 9 bytes (the longest of these values) are left in the input, leaving
|
||||
those to @ref parse_msgpack_value, which reads them the same way.
|
||||
|
||||
Out of line, so that @ref parse_msgpack_value stays small enough to be inlined.
|
||||
|
||||
The array is the one on top of @ref container_stack; its count of
|
||||
elements not yet read is updated.
|
||||
|
||||
@return whether the SAX parser accepted the values
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool parse_msgpack_scalar_run(std::true_type /*bulk*/)
|
||||
{
|
||||
// nothing pushes onto the stack while the run reads scalars
|
||||
std::size_t& remaining = container_stack.back().remaining;
|
||||
while (remaining != 0 && ia.bulk_remaining() >= 9)
|
||||
{
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
const unsigned char head = data[0];
|
||||
std::size_t size = 0;
|
||||
bool accepted = false;
|
||||
switch (head)
|
||||
{
|
||||
case 0xC0: // nil
|
||||
size = 1;
|
||||
accepted = sax->null();
|
||||
break;
|
||||
case 0xC2: // false
|
||||
size = 1;
|
||||
accepted = sax->boolean(false);
|
||||
break;
|
||||
case 0xC3: // true
|
||||
size = 1;
|
||||
accepted = sax->boolean(true);
|
||||
break;
|
||||
case 0xCA: // float 32
|
||||
size = 5;
|
||||
accepted = emit_float(load_big_endian<float>(data + 1));
|
||||
break;
|
||||
case 0xCB: // float 64
|
||||
size = 9;
|
||||
accepted = emit_float(load_big_endian<double>(data + 1));
|
||||
break;
|
||||
case 0xCC: // uint 8
|
||||
size = 2;
|
||||
accepted = emit_unsigned(data[1]);
|
||||
break;
|
||||
case 0xCD: // uint 16
|
||||
size = 3;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint16_t>(data + 1));
|
||||
break;
|
||||
case 0xCE: // uint 32
|
||||
size = 5;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint32_t>(data + 1));
|
||||
break;
|
||||
case 0xCF: // uint 64
|
||||
size = 9;
|
||||
accepted = emit_unsigned(load_big_endian<std::uint64_t>(data + 1));
|
||||
break;
|
||||
case 0xD0: // int 8
|
||||
size = 2;
|
||||
accepted = emit_signed(static_cast<std::int8_t>(data[1]));
|
||||
break;
|
||||
case 0xD1: // int 16
|
||||
size = 3;
|
||||
accepted = emit_signed(load_big_endian<std::int16_t>(data + 1));
|
||||
break;
|
||||
case 0xD2: // int 32
|
||||
size = 5;
|
||||
accepted = emit_signed(load_big_endian<std::int32_t>(data + 1));
|
||||
break;
|
||||
case 0xD3: // int 64
|
||||
size = 9;
|
||||
accepted = emit_signed(load_big_endian<std::int64_t>(data + 1));
|
||||
break;
|
||||
default:
|
||||
if (head <= 0x7F)
|
||||
{
|
||||
// positive fixint
|
||||
size = 1;
|
||||
accepted = sax->number_unsigned(static_cast<number_unsigned_t>(head));
|
||||
}
|
||||
else if (head >= 0xE0)
|
||||
{
|
||||
// negative fixint
|
||||
size = 1;
|
||||
accepted = sax->number_integer(static_cast<std::int8_t>(head));
|
||||
}
|
||||
else
|
||||
{
|
||||
// any other value is read by parse_msgpack_value
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// as if read by get() and get_number(): current is the last byte
|
||||
ia.bulk_skip(size);
|
||||
chars_read += size;
|
||||
current = data[size - 1];
|
||||
--remaining;
|
||||
if (JSON_HEDLEY_UNLIKELY(!accepted))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief open a MessagePack array and read the scalars it begins with (see
|
||||
@ref parse_msgpack_scalar_run)
|
||||
|
||||
An array of one element is not tried, for the reason given at
|
||||
@ref enter_cbor_array.
|
||||
|
||||
@param[in] len the number of elements
|
||||
@return whether the SAX parser accepted the array and the values read
|
||||
*/
|
||||
bool enter_msgpack_array(const std::size_t len)
|
||||
{
|
||||
return enter_array(len) && (len <= 1 || parse_msgpack_scalar_run(std::integral_constant<bool, bulk_scan> {}));
|
||||
}
|
||||
|
||||
/// input that is not contiguous: every value is read by parse_msgpack_value
|
||||
bool parse_msgpack_scalar_run(std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read a MessagePack value and everything nested inside it
|
||||
|
||||
@@ -3591,107 +3255,6 @@ class binary_reader
|
||||
return unexpected_byte("invalid byte", "value");
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief read the elements of an optimized array of numbers (type marker
|
||||
@a type) that are in the input, in one loop, without the
|
||||
per-element bookkeeping of @ref parse_ubjson_internal
|
||||
|
||||
Values are emitted as @ref get_ubjson_value emits them. Types other than
|
||||
the fixed-size integers and floats are left to @ref get_ubjson_value, as
|
||||
are the elements the input ends before.
|
||||
|
||||
@param[in,out] remaining the number of elements not yet read
|
||||
@param[in] type the type marker of the elements
|
||||
@return whether the SAX parser accepted the values
|
||||
*/
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool parse_ubjson_typed_run(std::size_t& remaining, const char_int_type type, std::true_type /*bulk*/)
|
||||
{
|
||||
const bool bjdata = input_format == input_format_t::bjdata;
|
||||
switch (type)
|
||||
{
|
||||
case 'U':
|
||||
return parse_ubjson_typed_run<std::uint8_t>(remaining);
|
||||
case 'i':
|
||||
return parse_ubjson_typed_run<std::int8_t>(remaining);
|
||||
case 'I':
|
||||
return parse_ubjson_typed_run<std::int16_t>(remaining);
|
||||
case 'l':
|
||||
return parse_ubjson_typed_run<std::int32_t>(remaining);
|
||||
case 'L':
|
||||
return parse_ubjson_typed_run<std::int64_t>(remaining);
|
||||
case 'u':
|
||||
return !bjdata || parse_ubjson_typed_run<std::uint16_t>(remaining);
|
||||
case 'm':
|
||||
return !bjdata || parse_ubjson_typed_run<std::uint32_t>(remaining);
|
||||
case 'M':
|
||||
return !bjdata || parse_ubjson_typed_run<std::uint64_t>(remaining);
|
||||
case 'd':
|
||||
return parse_ubjson_typed_run<float>(remaining);
|
||||
case 'D':
|
||||
return parse_ubjson_typed_run<double>(remaining);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// input that is not contiguous: every element is read by get_ubjson_value
|
||||
bool parse_ubjson_typed_run(std::size_t& /*remaining*/, const char_int_type /*type*/, std::false_type /*bulk*/) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename NumberType>
|
||||
bool parse_ubjson_typed_run(std::size_t& remaining)
|
||||
{
|
||||
constexpr std::size_t size = sizeof(NumberType);
|
||||
const std::size_t count = (std::min)(remaining, ia.bulk_remaining() / size);
|
||||
if (count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||
// UBJSON stores numbers big-endian, BJData little-endian
|
||||
const bool swap = is_little_endian != (input_format == input_format_t::bjdata);
|
||||
std::size_t read = 0;
|
||||
bool accepted = true;
|
||||
while (read < count && accepted)
|
||||
{
|
||||
NumberType number{};
|
||||
std::memcpy(&number, data + (read * size), size);
|
||||
if (swap)
|
||||
{
|
||||
byte_swap(number);
|
||||
}
|
||||
++read;
|
||||
accepted = emit_ubjson_number(number);
|
||||
}
|
||||
// as if read by get_number(): current is the last byte read
|
||||
ia.bulk_skip(read * size);
|
||||
chars_read += read * size;
|
||||
current = data[(read * size) - 1];
|
||||
remaining -= read;
|
||||
return accepted;
|
||||
}
|
||||
|
||||
template<typename NumberType, enable_if_t<std::is_floating_point<NumberType>::value, int> = 0>
|
||||
bool emit_ubjson_number(const NumberType number)
|
||||
{
|
||||
return emit_float(number);
|
||||
}
|
||||
|
||||
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& std::is_signed<NumberType>::value, int > = 0 >
|
||||
bool emit_ubjson_number(const NumberType number)
|
||||
{
|
||||
return emit_signed(number);
|
||||
}
|
||||
|
||||
template < typename NumberType, enable_if_t < std::is_integral<NumberType>::value&& !std::is_signed<NumberType>::value, int > = 0 >
|
||||
bool emit_ubjson_number(const NumberType number)
|
||||
{
|
||||
return emit_unsigned(number);
|
||||
}
|
||||
|
||||
/*!
|
||||
@return whether array creation completed
|
||||
*/
|
||||
@@ -3727,15 +3290,7 @@ class binary_reader
|
||||
return false;
|
||||
}
|
||||
|
||||
// the numbers that are in the input, in one loop; the rest (if
|
||||
// the input ends early) one by one below
|
||||
std::size_t remaining = size_and_type.first;
|
||||
if (!nd_is_char && JSON_HEDLEY_UNLIKELY(!parse_ubjson_typed_run(remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {})))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (std::size_t i = size_and_type.first - remaining; i < size_and_type.first; ++i)
|
||||
for (std::size_t i = 0; i < size_and_type.first; ++i)
|
||||
{
|
||||
if (nd_is_char)
|
||||
{
|
||||
@@ -3795,8 +3350,7 @@ class binary_reader
|
||||
container_stack.back().remaining = 0;
|
||||
}
|
||||
|
||||
return size_and_type.second == 0
|
||||
|| parse_ubjson_typed_run(container_stack.back().remaining, size_and_type.second, std::integral_constant<bool, bulk_scan> {});
|
||||
return true;
|
||||
}
|
||||
|
||||
return enter_array(detail::unknown_size());
|
||||
@@ -4137,13 +3691,13 @@ class binary_reader
|
||||
return sax->null();
|
||||
|
||||
case 0xFB:
|
||||
return sax->number_float(static_cast<number_float_t>(-1.0), no_source);
|
||||
return sax->number_float(static_cast<number_float_t>(-1.0), "");
|
||||
|
||||
case 0xFC:
|
||||
return sax->number_float(static_cast<number_float_t>(0.0), no_source);
|
||||
return sax->number_float(static_cast<number_float_t>(0.0), "");
|
||||
|
||||
case 0xFD:
|
||||
return sax->number_float(static_cast<number_float_t>(1.0), no_source);
|
||||
return sax->number_float(static_cast<number_float_t>(1.0), "");
|
||||
|
||||
case 0xFF: // empty string
|
||||
{
|
||||
@@ -4466,12 +4020,7 @@ class binary_reader
|
||||
`char_traits<char_type>::eof()` in that case.
|
||||
|
||||
@return character read from the input
|
||||
|
||||
@note always inlined: GCC otherwise stops inlining the stream adapter's
|
||||
get_character() into parse_cbor_internal once that function grows
|
||||
(+0.45 % instructions for CBOR from a std::istringstream with GCC 13)
|
||||
*/
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
char_int_type get()
|
||||
{
|
||||
++chars_read;
|
||||
@@ -4517,27 +4066,6 @@ class binary_reader
|
||||
return current;
|
||||
}
|
||||
|
||||
/// reverse the byte order of a double with a single byteswap(): GCC does
|
||||
/// not merge the byte-wise swap of the generic version into one
|
||||
static void byte_swap(double& number) noexcept
|
||||
{
|
||||
static_assert(sizeof(double) == sizeof(std::uint64_t), "double must have 64 bits");
|
||||
std::uint64_t bits{};
|
||||
std::memcpy(&bits, &number, sizeof(bits));
|
||||
bits = byteswap(bits);
|
||||
std::memcpy(&number, &bits, sizeof(bits));
|
||||
}
|
||||
|
||||
/// reverse the byte order of a float with a single byteswap()
|
||||
static void byte_swap(float& number) noexcept
|
||||
{
|
||||
static_assert(sizeof(float) == sizeof(std::uint32_t), "float must have 32 bits");
|
||||
std::uint32_t bits{};
|
||||
std::memcpy(&bits, &number, sizeof(bits));
|
||||
bits = byteswap(bits);
|
||||
std::memcpy(&number, &bits, sizeof(bits));
|
||||
}
|
||||
|
||||
template<class NumberType>
|
||||
static void byte_swap(NumberType& number)
|
||||
{
|
||||
@@ -4673,7 +4201,7 @@ class binary_reader
|
||||
{
|
||||
return out_of_range_error(exception_id::number_overflow, "number overflow", "value");
|
||||
}
|
||||
return sax->number_float(result, no_source);
|
||||
return sax->number_float(result, "");
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4733,7 +4261,7 @@ class binary_reader
|
||||
}();
|
||||
return sax->number_float((half & 0x8000u) != 0
|
||||
? static_cast<number_float_t>(-val)
|
||||
: static_cast<number_float_t>(val), no_source);
|
||||
: static_cast<number_float_t>(val), "");
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -4765,19 +4293,6 @@ class binary_reader
|
||||
return get_bytes(len, "string", result);
|
||||
}
|
||||
|
||||
/// whether @a s is valid UTF-8: ASCII eight bytes at a time, the rest
|
||||
/// sequence by sequence (as the binary writers check strings)
|
||||
static bool is_valid_utf8_string(const string_t& s, std::true_type /*is_byte*/) noexcept
|
||||
{
|
||||
return valid_utf8_string(reinterpret_cast<const unsigned char*>(s.data()), s.size());
|
||||
}
|
||||
|
||||
/// whether @a s, of a character type wider than a byte, is valid UTF-8
|
||||
static bool is_valid_utf8_string(const string_t& s, std::false_type /*is_byte*/) noexcept
|
||||
{
|
||||
return is_valid_utf8(s);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate a decoded text string (value or object key) against @ref error_handler
|
||||
|
||||
@@ -4797,16 +4312,7 @@ class binary_reader
|
||||
*/
|
||||
bool check_string_utf8(string_t& result, const char* context)
|
||||
{
|
||||
return error_handler == error_handler_t::keep || check_string_utf8_handler(result, context);
|
||||
}
|
||||
|
||||
/// @ref check_string_utf8 for an error handler other than @ref
|
||||
/// error_handler_t::keep; out of line, so that reading strings without a
|
||||
/// check does not pay for the code of the check
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
bool check_string_utf8_handler(string_t& result, const char* context)
|
||||
{
|
||||
if (is_valid_utf8_string(result, std::integral_constant<bool, sizeof(typename string_t::value_type) == 1>()))
|
||||
if (error_handler == error_handler_t::keep || is_valid_utf8(result))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -5080,10 +4586,6 @@ class binary_reader
|
||||
/// the SAX parser
|
||||
json_sax_t* sax = nullptr;
|
||||
|
||||
/// the (empty) source text of every number_float event: binary formats
|
||||
/// have none, and a string_t built for each float would cost more
|
||||
const string_t no_source{};
|
||||
|
||||
/// the containers that have been opened and not closed yet; see @ref container_frame
|
||||
std::vector<container_frame> container_stack{};
|
||||
|
||||
|
||||
@@ -545,9 +545,12 @@ class input_stream_adapter
|
||||
std::char_traits<char>::int_type get_character_slow()
|
||||
{
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
// the get area is exhausted (get_character() handles the other case),
|
||||
// so sbumpc() refills it
|
||||
save_token_segment();
|
||||
// sbumpc() refills the get area if it is exhausted
|
||||
const bool refill = streambuf_access::get_ptr(sb) == streambuf_access::get_end(sb);
|
||||
if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
save_token_segment();
|
||||
}
|
||||
#endif
|
||||
|
||||
auto res = sb->sbumpc();
|
||||
@@ -560,13 +563,17 @@ class input_stream_adapter
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
#if !JSON_DISABLE_STREAM_BULK_SCAN
|
||||
else
|
||||
else if (JSON_HEDLEY_UNLIKELY(refill))
|
||||
{
|
||||
// the consumed character is at gptr() - 1, unless the streambuf
|
||||
// has no get area and returned it from uflow()
|
||||
const char* const next = streambuf_access::get_ptr(sb);
|
||||
after_refill(res, next != nullptr && next != streambuf_access::get_begin(sb));
|
||||
}
|
||||
else
|
||||
{
|
||||
current_in_get_area = true;
|
||||
}
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -230,10 +230,7 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer has read the first character of the container, so
|
||||
// subtract 1 from the position to get the correct start position.
|
||||
// (npos: a value without a source position, see materialize() of
|
||||
// json_view)
|
||||
const std::size_t read = lexer->get_chars_read_total();
|
||||
v.start_position = read == std::string::npos ? std::string::npos : read - 1;
|
||||
v.start_position = lexer->get_chars_read_total() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,12 +245,6 @@ struct diagnostic_positions
|
||||
{
|
||||
// Lexer's position is past the closing brace or bracket, so set that as the end position.
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
if (v.end_position == std::string::npos)
|
||||
{
|
||||
// a range has both ends or none (exception::diagnostics()
|
||||
// prints only complete ones)
|
||||
v.start_position = std::string::npos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,12 +261,6 @@ struct diagnostic_positions
|
||||
// The start position will be set below based on the length of the string representation
|
||||
// of the value.
|
||||
v.end_position = lexer->get_chars_read_total();
|
||||
if (v.end_position == std::string::npos)
|
||||
{
|
||||
// a value without a source position
|
||||
v.start_position = std::string::npos;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v.type())
|
||||
{
|
||||
@@ -488,12 +473,8 @@ read up to it.
|
||||
@tparam Collect whether to collect the values of containers of unknown size on
|
||||
a stack (only for a caller that does not use the value after a
|
||||
parse error)
|
||||
@tparam LexerType the type of the lexer that reports the source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS); only diagnostic_positions uses
|
||||
it, so any type with its three functions works (as
|
||||
materialize() of json_view uses it)
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false, typename LexerType = lexer<BasicJsonType, InputAdapterType>>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect = false>
|
||||
class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
public:
|
||||
@@ -502,7 +483,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using binary_t = typename BasicJsonType::binary_t;
|
||||
using lexer_t = LexerType;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||
|
||||
/*!
|
||||
@param[in,out] r reference to a JSON value that is manipulated while
|
||||
@@ -999,7 +980,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
ordered_data.members[f.ordered.dest_member].second = std::move(value);
|
||||
}
|
||||
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {} // LCOV_EXCL_LINE
|
||||
static void place_member(const frame& /*f*/, BasicJsonType&& /*value*/, std::false_type /*indexed*/) noexcept {}
|
||||
|
||||
/// store the members of a new object directly in it
|
||||
bool start_stored_object(std::size_t len)
|
||||
@@ -1303,7 +1284,7 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
{
|
||||
// a new member is null and holds nothing: construct the value
|
||||
// over it instead of destroying and swapping
|
||||
construct_member(member, std::forward<Value>(v), std::is_nothrow_constructible<BasicJsonType, Value> {});
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1312,26 +1293,6 @@ class json_sax_dom_parser : public json_sax_dom_error_state
|
||||
}
|
||||
}
|
||||
|
||||
/// constructing the value cannot throw (for example a number or a boolean): construct it in place
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::true_type /*nothrow*/)
|
||||
{
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::forward<Value>(v));
|
||||
}
|
||||
|
||||
/// constructing the value can throw (for example a container or a string), and a
|
||||
/// constructor that was interrupted must not have touched the member (#5985):
|
||||
/// create the value first, then move it over the member (the move constructor is
|
||||
/// noexcept)
|
||||
template<typename Value>
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void construct_member(BasicJsonType& member, Value&& v, std::false_type /*may throw*/)
|
||||
{
|
||||
BasicJsonType value(std::forward<Value>(v));
|
||||
::new (static_cast<void*>(&member)) BasicJsonType(std::move(value));
|
||||
}
|
||||
|
||||
/// a custom base class may care about the lifetime of the value
|
||||
template<typename Value>
|
||||
static void set_member(BasicJsonType& member, Value&& v, std::false_type /*custom base*/)
|
||||
@@ -1757,7 +1718,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
key_stack.push_back(val);
|
||||
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep)
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
// a single lookup: insert the placeholder, or find the existing member
|
||||
@@ -1814,7 +1775,7 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
|
||||
const string_t object_key = pop_container();
|
||||
|
||||
if (!ref_stack.empty() && ref_stack.back())
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
|
||||
{
|
||||
// remove discarded value
|
||||
remove_discarded_value(*ref_stack.back(), object_key);
|
||||
@@ -1980,7 +1941,8 @@ class json_sax_dom_callback_parser : public json_sax_dom_error_state
|
||||
*/
|
||||
string_t current_key() const
|
||||
{
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
|
||||
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object()
|
||||
&& !key_stack.empty())
|
||||
{
|
||||
return key_stack.back();
|
||||
}
|
||||
|
||||
@@ -209,13 +209,8 @@ constexpr bool input_adapter_bulk_window(std::false_type /*detected*/)
|
||||
@brief lexical analysis
|
||||
|
||||
This class organizes the lexical analysis during JSON deserialization.
|
||||
|
||||
DiscardValues: the caller only needs the token types and never looks at the values of number and string tokens (only
|
||||
accept(), which parses through json_sax_acceptor). A template parameter rather than a member, so that the lexer of
|
||||
parse() is compiled without the paths of accept(): with both string scans in one scan(), parse() was up to 2 % slower
|
||||
on Apple M1.
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool DiscardValues = false>
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -273,9 +268,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
public:
|
||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||
|
||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept
|
||||
explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false, bool discard_number_values_ = false) noexcept
|
||||
: ia(std::move(adapter))
|
||||
, ignore_comments(ignore_comments_)
|
||||
, discard_number_values(discard_number_values_)
|
||||
{
|
||||
// the token string of an error before the first token is everything
|
||||
// read so far
|
||||
@@ -300,16 +296,14 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
/// calls would (including the position and token bookkeeping of a
|
||||
/// streaming adapter, see bulk_consume) and leaves @a current holding the last of
|
||||
/// the 4 digits, just as the last such get() would; the codepoint is
|
||||
/// written to @a out. Makes no state change and returns false for fewer
|
||||
/// than 4 remaining bytes or any of the 4 bytes not being a hex digit, so
|
||||
/// the caller falls back unchanged to the per-character loop, which then
|
||||
/// reports the same diagnostic (stopping at the first invalid digit) as
|
||||
/// before this optimization.
|
||||
/// written to @a out. Makes no state change and returns false for a
|
||||
/// pending unget, fewer than 4 remaining bytes, or any of the 4 bytes not
|
||||
/// being a hex digit, so the caller falls back unchanged to the
|
||||
/// per-character loop, which then reports the same diagnostic (stopping
|
||||
/// at the first invalid digit) as before this optimization.
|
||||
bool get_codepoint_bulk(std::true_type /*bulk*/, int& out)
|
||||
{
|
||||
// no unget is pending inside a string (see get_after_string_run())
|
||||
JSON_ASSERT(!next_unget);
|
||||
if (ia.bulk_remaining() < 4)
|
||||
if (next_unget || ia.bulk_remaining() < 4)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -394,8 +388,6 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
checks if it is inside the range. If a violation was detected, set up an
|
||||
error message and return false. Otherwise, return true.
|
||||
|
||||
@param[in] keep whether the bytes are added to token_buffer (false for
|
||||
accept(), see scan())
|
||||
@param[in] ranges list of integers; interpreted as list of pairs of
|
||||
inclusive lower and upper bound, respectively
|
||||
|
||||
@@ -404,18 +396,17 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
|
||||
@return true if and only if no range violation was detected
|
||||
*/
|
||||
template<bool Keep>
|
||||
bool next_byte_in_range(std::integral_constant<bool, Keep> keep, std::initializer_list<char_int_type> ranges)
|
||||
bool next_byte_in_range(std::initializer_list<char_int_type> ranges)
|
||||
{
|
||||
JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);
|
||||
add_string_char(keep, current);
|
||||
add(current);
|
||||
|
||||
for (auto range = ranges.begin(); range != ranges.end(); ++range)
|
||||
{
|
||||
get();
|
||||
if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)
|
||||
{
|
||||
add_string_char(keep, current);
|
||||
add(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -428,21 +419,20 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
|
||||
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||
/// well-formed UTF-8 sequences starting at the current read position (only
|
||||
/// skip it if @a keep is false), then
|
||||
/// well-formed UTF-8 sequences starting at the current read position, then
|
||||
/// read the first byte that needs individual handling (the closing quote, an
|
||||
/// escape, a control character, or an ill-formed UTF-8 byte) like get().
|
||||
/// That byte is taken from the buffer that was just scanned. Through get(),
|
||||
/// Apple Clang merged this read with the other get() calls of the scan
|
||||
/// function, which load the read position again right after it was stored
|
||||
/// (3% of json::accept() on jeopardy.json on Apple M1).
|
||||
template<bool Keep>
|
||||
char_int_type get_after_string_run(std::true_type /*bulk*/, std::integral_constant<bool, Keep> keep)
|
||||
char_int_type get_after_string_run(std::true_type /*bulk*/)
|
||||
{
|
||||
// scan_string() is entered from scan_after_whitespace(), whose current
|
||||
// was read by skip_whitespace() (its first get() clears a pending
|
||||
// unget), and nothing in scan_string() calls unget()
|
||||
JSON_ASSERT(!next_unget);
|
||||
// a pending unget must be consumed through the normal path first
|
||||
if (next_unget)
|
||||
{
|
||||
return get();
|
||||
}
|
||||
const std::size_t remaining = ia.bulk_remaining();
|
||||
if (remaining == 0)
|
||||
{
|
||||
@@ -454,7 +444,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
const std::size_t pos = lexer_string_bulk_run(data, remaining);
|
||||
if (pos != 0)
|
||||
{
|
||||
append_string_run(keep, data, pos);
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||
bulk_consume(reinterpret_cast<const char*>(data), pos);
|
||||
}
|
||||
if (pos == remaining)
|
||||
@@ -498,8 +488,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
}
|
||||
|
||||
/// streaming input: no bulk fast path
|
||||
template<bool Keep>
|
||||
char_int_type get_after_string_run(std::false_type /*bulk*/, std::integral_constant<bool, Keep> /*keep*/)
|
||||
char_int_type get_after_string_run(std::false_type /*bulk*/)
|
||||
{
|
||||
return get();
|
||||
}
|
||||
@@ -513,18 +502,13 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
may contain \0 bytes), and token_buffer.size() is the number of bytes in the
|
||||
string.
|
||||
|
||||
@param[in] keep whether the string is copied into token_buffer; if false
|
||||
(accept(), see scan()), it is only checked, with the same
|
||||
errors, and token_buffer stays empty
|
||||
|
||||
@return token_type::value_string if string could be successfully scanned,
|
||||
token_type::parse_error otherwise
|
||||
|
||||
@note In case of errors, variable error_message contains a textual
|
||||
description.
|
||||
*/
|
||||
template<bool Keep>
|
||||
token_type scan_string(std::integral_constant<bool, Keep> keep)
|
||||
token_type scan_string()
|
||||
{
|
||||
// reset token_buffer (ignore opening quote)
|
||||
reset();
|
||||
@@ -536,7 +520,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
// bulk-consume ordinary characters from contiguous input, then
|
||||
// get the next character, the one that ends the run
|
||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>(), keep))
|
||||
switch (get_after_string_run(std::integral_constant<bool, bulk_scan>()))
|
||||
{
|
||||
// end of file while parsing the string
|
||||
case char_traits<char_type>::eof():
|
||||
@@ -558,35 +542,35 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
// quotation mark
|
||||
case '\"':
|
||||
add_string_char(keep, '\"');
|
||||
add('\"');
|
||||
break;
|
||||
// reverse solidus
|
||||
case '\\':
|
||||
add_string_char(keep, '\\');
|
||||
add('\\');
|
||||
break;
|
||||
// solidus
|
||||
case '/':
|
||||
add_string_char(keep, '/');
|
||||
add('/');
|
||||
break;
|
||||
// backspace
|
||||
case 'b':
|
||||
add_string_char(keep, '\b');
|
||||
add('\b');
|
||||
break;
|
||||
// form feed
|
||||
case 'f':
|
||||
add_string_char(keep, '\f');
|
||||
add('\f');
|
||||
break;
|
||||
// line feed
|
||||
case 'n':
|
||||
add_string_char(keep, '\n');
|
||||
add('\n');
|
||||
break;
|
||||
// carriage return
|
||||
case 'r':
|
||||
add_string_char(keep, '\r');
|
||||
add('\r');
|
||||
break;
|
||||
// tab
|
||||
case 't':
|
||||
add_string_char(keep, '\t');
|
||||
add('\t');
|
||||
break;
|
||||
|
||||
// unicode escapes
|
||||
@@ -654,7 +638,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);
|
||||
|
||||
// translate codepoint into bytes
|
||||
add_codepoint(keep, codepoint);
|
||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||
{
|
||||
add(static_cast<char_int_type>(byte));
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -837,7 +824,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case 0x7E:
|
||||
case 0x7F:
|
||||
{
|
||||
add_string_char(keep, current);
|
||||
add(current);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -873,7 +860,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case 0xDE:
|
||||
case 0xDF:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range(keep, {0x80, 0xBF})))
|
||||
if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -883,7 +870,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// U+0800..U+0FFF: bytes E0 A0..BF 80..BF
|
||||
case 0xE0:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0xA0, 0xBF, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -907,7 +894,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case 0xEE:
|
||||
case 0xEF:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -917,7 +904,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// U+D000..U+D7FF: bytes ED 80..9F 80..BF
|
||||
case 0xED:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x9F, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -927,7 +914,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
|
||||
case 0xF0:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -939,7 +926,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case 0xF2:
|
||||
case 0xF3:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -949,7 +936,7 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
|
||||
case 0xF4:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range(keep, {0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))
|
||||
{
|
||||
return token_type::parse_error;
|
||||
}
|
||||
@@ -1411,7 +1398,7 @@ scan_number_done:
|
||||
return token_type::value_unsigned;
|
||||
}
|
||||
}
|
||||
else // value_integer (the callers handle value_float themselves)
|
||||
else if (number_type == token_type::value_integer)
|
||||
{
|
||||
if (parse_integer_signed(first, last, value_integer))
|
||||
{
|
||||
@@ -1440,7 +1427,7 @@ scan_number_done:
|
||||
token_type convert_number(token_type number_type, std::size_t mantissa_end)
|
||||
{
|
||||
// accept() only needs to know whether the input is valid, so it sets
|
||||
// discard_values (see json.hpp), and an integer token whose
|
||||
// discard_number_values (see json.hpp), and an integer token whose
|
||||
// digit count shows that it fits is reported without calling
|
||||
// convert_integer(). A number with up to 18 digits always fits into
|
||||
// both std::uint64_t and std::int64_t (18 nines is about 1e18, below
|
||||
@@ -1455,7 +1442,7 @@ scan_number_done:
|
||||
// json_sax_acceptor, whose number callbacks discard their argument and
|
||||
// return true, and the parser rejects neither integers nor finite
|
||||
// floats. value_unsigned/value_integer are left unset here, so a caller
|
||||
// that reads the converted value must not set discard_values.
|
||||
// that reads the converted value must not set discard_number_values.
|
||||
//
|
||||
// On contiguous input, scan_number_bulk_contiguous() converts integer
|
||||
// tokens itself and does not pass them to this function, unless
|
||||
@@ -1463,7 +1450,7 @@ scan_number_done:
|
||||
// reached for input without bulk access (e.g. streams), with
|
||||
// JSON_DIAGNOSTIC_POSITIONS, or when scan_number_bulk_contiguous()
|
||||
// falls back to scan_number().
|
||||
if (discard_values())
|
||||
if (discard_number_values)
|
||||
{
|
||||
constexpr std::size_t safe_digit_count = 18;
|
||||
if (number_type == token_type::value_unsigned && token_buffer.size() <= safe_digit_count)
|
||||
@@ -1517,9 +1504,11 @@ scan_number_done:
|
||||
*/
|
||||
token_type scan_number_bulk_contiguous()
|
||||
{
|
||||
// called right after scan_after_whitespace() read current (see
|
||||
// get_after_string_run()), so no unget is pending
|
||||
JSON_ASSERT(!next_unget);
|
||||
// a pending unget offsets the buffer position from current; fall back
|
||||
if (next_unget)
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
const std::size_t rem = ia.bulk_remaining();
|
||||
if (rem == 0)
|
||||
{
|
||||
@@ -1540,7 +1529,11 @@ scan_number_done:
|
||||
if (data[0] == '-')
|
||||
{
|
||||
number_type = token_type::value_integer;
|
||||
i = 1; // avail >= 2: rem == 0 returned above
|
||||
i = 1;
|
||||
if (i >= avail)
|
||||
{
|
||||
return token_type::uninitialized;
|
||||
}
|
||||
}
|
||||
const std::size_t integer_begin = i;
|
||||
if (data[i] == '0')
|
||||
@@ -1708,7 +1701,7 @@ scan_number_done:
|
||||
// w < 10^19, so a value below 10^max_exponent10 cannot overflow, and a
|
||||
// value that underflows is a finite number as well. Only a token that
|
||||
// may overflow has to be converted to find out.
|
||||
if (JSON_HEDLEY_UNLIKELY(discard_values() && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||
if (JSON_HEDLEY_UNLIKELY(discard_number_values && s.exponent + 19 <= std::numeric_limits<number_float_t>::max_exponent10))
|
||||
{
|
||||
value_float = 0;
|
||||
return true;
|
||||
@@ -1782,10 +1775,7 @@ scan_number_done:
|
||||
/// the error at the right character
|
||||
JSON_HEDLEY_ALWAYS_INLINE bool scan_literal_bulk(const char* rest, const std::size_t length, std::true_type /*bulk*/)
|
||||
{
|
||||
// called right after scan_after_whitespace() read current (see
|
||||
// get_after_string_run()), so no unget is pending
|
||||
JSON_ASSERT(!next_unget);
|
||||
if (ia.bulk_remaining() < length
|
||||
if (next_unget || ia.bulk_remaining() < length
|
||||
|| std::memcmp(ia.bulk_data(), rest, length) != 0)
|
||||
{
|
||||
return false;
|
||||
@@ -2043,34 +2033,6 @@ scan_number_done:
|
||||
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||
}
|
||||
|
||||
/// add a character of a string token; accept() checks the string but does
|
||||
/// not keep its value (@a keep is false)
|
||||
void add_string_char(std::true_type /*keep*/, char_int_type c)
|
||||
{
|
||||
token_buffer.push_back(static_cast<typename string_t::value_type>(c));
|
||||
}
|
||||
|
||||
static void add_string_char(std::false_type /*keep*/, char_int_type /*c*/) noexcept {}
|
||||
|
||||
/// add the UTF-8 bytes of a code point from a \\u escape to a string token
|
||||
void add_codepoint(std::true_type /*keep*/, int codepoint)
|
||||
{
|
||||
encode_utf8(static_cast<std::uint32_t>(codepoint), [this](std::uint32_t byte)
|
||||
{
|
||||
add(static_cast<char_int_type>(byte));
|
||||
});
|
||||
}
|
||||
|
||||
static void add_codepoint(std::false_type /*keep*/, int /*codepoint*/) noexcept {}
|
||||
|
||||
/// append a run of ordinary string characters (see get_after_string_run())
|
||||
void append_string_run(std::true_type /*keep*/, const unsigned char* data, std::size_t n)
|
||||
{
|
||||
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), n);
|
||||
}
|
||||
|
||||
static void append_string_run(std::false_type /*keep*/, const unsigned char* /*data*/, std::size_t /*n*/) noexcept {}
|
||||
|
||||
public:
|
||||
/////////////////////
|
||||
// value getters
|
||||
@@ -2465,9 +2427,7 @@ scan_number_done:
|
||||
|
||||
// string
|
||||
case '\"':
|
||||
// accept() (DiscardValues) checks strings without copying them
|
||||
// into token_buffer
|
||||
return scan_string(keep_strings {});
|
||||
return scan_string();
|
||||
|
||||
// number
|
||||
case '-':
|
||||
@@ -2552,19 +2512,11 @@ scan_number_done:
|
||||
std::size_t decimal_point_position = std::string::npos;
|
||||
|
||||
/// whether the caller only needs the token types and never looks at the
|
||||
/// values of number and string tokens; DiscardValues, set only by accept(), which parses
|
||||
/// through json_sax_acceptor. When set, convert_number() skips converting
|
||||
/// integer tokens whose digit count guarantees that they fit into 64 bits
|
||||
/// (see there), floats are converted only when they may overflow (see
|
||||
/// convert_accumulated_float()), and strings are checked without being
|
||||
/// copied into token_buffer (see scan())
|
||||
static bool discard_values() noexcept
|
||||
{
|
||||
return DiscardValues;
|
||||
}
|
||||
|
||||
/// the tag of scan_string(): whether string tokens are kept
|
||||
using keep_strings = typename std::conditional<DiscardValues, std::false_type, std::true_type>::type;
|
||||
/// converted numeric values; set only by accept(), which parses through
|
||||
/// json_sax_acceptor. When set, convert_number() skips converting integer
|
||||
/// tokens whose digit count guarantees that they fit into 64 bits (see
|
||||
/// there)
|
||||
const bool discard_number_values = false;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -44,13 +44,6 @@
|
||||
// use std::from_chars or std::strtold. They are free functions so the lexer
|
||||
// stays focused on scanning (see lexer::convert_number()) and so that other
|
||||
// parsers of JSON text can convert tokens exactly like it does.
|
||||
//
|
||||
// The Eisel-Lemire conversion (eisel_lemire()), the digit comparison for the
|
||||
// cases it cannot decide, and the eight-digits-at-a-time helpers
|
||||
// (is_eight_digits(), parse_eight_digits()) are adapted from fast_float
|
||||
// (https://github.com/fastfloat/fast_float, Daniel Lemire and contributors,
|
||||
// available under the MIT, Apache 2.0, and Boost Software licenses; used here
|
||||
// under the MIT license). The table of powers of five is in pow5_table.hpp.
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
|
||||
@@ -56,8 +56,8 @@ using parser_callback_t =
|
||||
template<typename SAX>
|
||||
struct sax_ignores_number_text : std::false_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect, LexerType>> : std::true_type {};
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
struct sax_ignores_number_text<json_sax_dom_parser<BasicJsonType, InputAdapterType, Collect>> : std::true_type {};
|
||||
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
struct sax_ignores_number_text<json_sax_dom_callback_parser<BasicJsonType, InputAdapterType>> : std::true_type {};
|
||||
@@ -72,14 +72,14 @@ This class implements a parser for JSON text. Nested arrays and objects are trac
|
||||
stack instead of recursion, so deeply nested input does not exhaust the call stack, and what is read
|
||||
is reported as SAX events.
|
||||
*/
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool DiscardValues = false>
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
class parser
|
||||
{
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using number_float_t = typename BasicJsonType::number_float_t;
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType, DiscardValues>;
|
||||
using lexer_t = lexer<BasicJsonType, InputAdapterType>;
|
||||
using token_type = typename lexer_t::token_type;
|
||||
|
||||
public:
|
||||
@@ -88,9 +88,10 @@ class parser
|
||||
parser_callback_t<BasicJsonType> cb = nullptr,
|
||||
const bool allow_exceptions_ = true,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas_ = false)
|
||||
const bool ignore_trailing_commas_ = false,
|
||||
const bool discard_number_values_ = false)
|
||||
: callback(std::move(cb))
|
||||
, m_lexer(std::move(adapter), ignore_comments)
|
||||
, m_lexer(std::move(adapter), ignore_comments, discard_number_values_)
|
||||
, allow_exceptions(allow_exceptions_)
|
||||
, ignore_trailing_commas(ignore_trailing_commas_)
|
||||
{
|
||||
@@ -573,7 +574,14 @@ class parser
|
||||
|
||||
std::string exception_message(const token_type expected, const std::string& context)
|
||||
{
|
||||
std::string error_msg = concat("syntax error while parsing ", context, " - ");
|
||||
std::string error_msg = "syntax error ";
|
||||
|
||||
if (!context.empty())
|
||||
{
|
||||
error_msg += concat("while parsing ", context, ' ');
|
||||
}
|
||||
|
||||
error_msg += "- ";
|
||||
|
||||
if (last_token == token_type::parse_error)
|
||||
{
|
||||
@@ -591,7 +599,7 @@ class parser
|
||||
}
|
||||
|
||||
return error_msg;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
private:
|
||||
/// callback function
|
||||
|
||||
@@ -30,13 +30,6 @@
|
||||
// NLOHMANN_JSON_NO_TARGET_ATTRIBUTE (for compilers that ignore the target
|
||||
// attribute and then reject the SSSE3 intrinsics) keeps the check portable
|
||||
// without disabling the SSE2 code.
|
||||
//
|
||||
// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson
|
||||
// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John
|
||||
// Keiser, and contributors, available under the Apache 2.0 and MIT licenses;
|
||||
// used here under the MIT license): the "lookup4" algorithm and tables of
|
||||
// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per
|
||||
// Byte" (2021).
|
||||
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
#include <arm_neon.h>
|
||||
#define JSON_SCAN_NEON 1
|
||||
@@ -322,78 +315,6 @@ JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline bool vector_utf8_run(cons
|
||||
block_out = block;
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief skip 16-byte blocks of well-formed UTF-8 at p (a character boundary)
|
||||
|
||||
Like vector_utf8_run, but quotes, backslashes, and control characters are
|
||||
ordinary characters. Returns the start of the first block in which the check
|
||||
found an error (which may belong to a sequence begun in the block before) or
|
||||
where fewer than 16 bytes are left; a sequence may be cut at that point, so the
|
||||
caller resumes at its start, one sequence at a time.
|
||||
*/
|
||||
JSON_SCAN_SSSE3_TARGET JSON_HEDLEY_NEVER_INLINE inline const unsigned char* vector_utf8_valid_blocks(const unsigned char* block, const unsigned char* e) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
#if JSON_SCAN_NEON
|
||||
const uint8x16_t t1h = vld1q_u8(lookup::byte_1_high.data());
|
||||
const uint8x16_t t1l = vld1q_u8(lookup::byte_1_low.data());
|
||||
const uint8x16_t t2h = vld1q_u8(lookup::byte_2_high.data());
|
||||
uint8x16_t prev = vdupq_n_u8(0);
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const uint8x16_t in = vld1q_u8(block);
|
||||
if (vmaxvq_u8(vorrq_u8(in, prev)) < 0x80)
|
||||
{
|
||||
// ASCII here, and no sequence from the block before to finish
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
const uint8x16_t prev1 = vextq_u8(prev, in, 15);
|
||||
const uint8x16_t sc = vandq_u8(vandq_u8(vqtbl1q_u8(t1h, vshrq_n_u8(prev1, 4)), vqtbl1q_u8(t1l, vandq_u8(prev1, vdupq_n_u8(0x0F)))), vqtbl1q_u8(t2h, vshrq_n_u8(in, 4)));
|
||||
const uint8x16_t must23 = vorrq_u8(vqsubq_u8(vextq_u8(prev, in, 14), vdupq_n_u8(0xE0 - 0x80)), vqsubq_u8(vextq_u8(prev, in, 13), vdupq_n_u8(0xF0 - 0x80)));
|
||||
const uint8x16_t err = veorq_u8(vandq_u8(must23, vdupq_n_u8(0x80)), sc);
|
||||
if (vmaxvq_u8(err) != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#else
|
||||
const __m128i t1h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t1l = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_1_low.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i t2h = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(lookup::byte_2_high.data()))); // NOLINT(bugprone-casting-through-void)
|
||||
const __m128i nibble = _mm_set1_epi8(0x0F);
|
||||
const __m128i zero = _mm_setzero_si128();
|
||||
__m128i prev = zero;
|
||||
while (e - block >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(block))); // NOLINT(bugprone-casting-through-void)
|
||||
if (_mm_movemask_epi8(_mm_or_si128(in, prev)) == 0)
|
||||
{
|
||||
prev = in;
|
||||
block += 16;
|
||||
continue;
|
||||
}
|
||||
const __m128i prev1 = _mm_alignr_epi8(in, prev, 15);
|
||||
const __m128i sc = _mm_and_si128(_mm_and_si128(_mm_shuffle_epi8(t1h, _mm_and_si128(_mm_srli_epi16(prev1, 4), nibble)),
|
||||
_mm_shuffle_epi8(t1l, _mm_and_si128(prev1, nibble))),
|
||||
_mm_shuffle_epi8(t2h, _mm_and_si128(_mm_srli_epi16(in, 4), nibble)));
|
||||
const __m128i must23 = _mm_or_si128(_mm_subs_epu8(_mm_alignr_epi8(in, prev, 14), _mm_set1_epi8(0xE0 - 0x80)),
|
||||
_mm_subs_epu8(_mm_alignr_epi8(in, prev, 13), _mm_set1_epi8(0xF0 - 0x80)));
|
||||
const __m128i err = _mm_xor_si128(_mm_and_si128(must23, _mm_set1_epi8(static_cast<char>(-128))), sc);
|
||||
if (_mm_movemask_epi8(_mm_cmpeq_epi8(err, zero)) != 0xFFFF)
|
||||
{
|
||||
break;
|
||||
}
|
||||
prev = in;
|
||||
block += 16;
|
||||
}
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -90,34 +90,6 @@ JSON_HEDLEY_ALWAYS_INLINE std::size_t find_string_special(const unsigned char* d
|
||||
return n;
|
||||
}
|
||||
|
||||
// return the index of the first 0x00 byte in [data, data+n), or n if there is
|
||||
// none; scans 8 bytes at a time (BSON's C-style strings)
|
||||
JSON_HEDLEY_ALWAYS_INLINE std::size_t find_zero_byte(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
std::size_t i = 0;
|
||||
for (; i + 8 <= n; i += 8)
|
||||
{
|
||||
const std::uint64_t v = read_eight_bytes(data + i);
|
||||
const std::uint64_t zero = (v - ones) & ~v & high;
|
||||
if (zero != 0)
|
||||
{
|
||||
// the lowest flagged byte is the first zero byte (see
|
||||
// find_string_special())
|
||||
return i + (static_cast<std::size_t>(count_trailing_zeros(zero)) / 8);
|
||||
}
|
||||
}
|
||||
for (; i < n; ++i)
|
||||
{
|
||||
if (data[i] == 0x00)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// classify a byte as one the serializer must NOT copy verbatim when
|
||||
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||
@@ -264,64 +236,6 @@ inline std::size_t valid_utf8_prefix(const unsigned char* data, std::size_t n) n
|
||||
return pos;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether [data, data+n) is valid UTF-8, for checking whole strings
|
||||
|
||||
The same answer as valid_utf8_prefix(data, n) == n. Short ASCII strings, the
|
||||
most common keys and values, are decided in one test, and long strings are
|
||||
checked 16 bytes at a time where the vector check is available.
|
||||
*/
|
||||
inline bool valid_utf8_string(const unsigned char* data, std::size_t n) noexcept
|
||||
{
|
||||
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||
if (n <= 16)
|
||||
{
|
||||
// two overlapping words cover 8 to 16 bytes, one partial word fewer
|
||||
std::uint64_t first = 0;
|
||||
std::uint64_t last = 0;
|
||||
if (n >= 8)
|
||||
{
|
||||
std::memcpy(&first, data, sizeof(first));
|
||||
std::memcpy(&last, data + n - 8, sizeof(last));
|
||||
}
|
||||
else if (n != 0)
|
||||
{
|
||||
std::memcpy(&first, data, n);
|
||||
}
|
||||
if (((first | last) & high) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
else if (n >= 32
|
||||
#if JSON_SCAN_SSSE3_DISPATCH
|
||||
&& cpu_has_ssse3()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
const unsigned char* block = vector_utf8_valid_blocks(data, data + n);
|
||||
// the rest from the start of a sequence that may be cut at block
|
||||
for (int i = 1; i <= 3 && block - i >= data; ++i)
|
||||
{
|
||||
const unsigned char c = block[-i];
|
||||
if (c < 0x80)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (c >= 0xC0)
|
||||
{
|
||||
block -= i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const auto checked = static_cast<std::size_t>(block - data);
|
||||
return checked + valid_utf8_prefix(block, n - checked) == n;
|
||||
}
|
||||
#endif
|
||||
return valid_utf8_prefix(data, n) == n;
|
||||
}
|
||||
|
||||
#if JSON_SCAN_VECTOR_UTF8
|
||||
/// the end of scan_string_vector from block, where the vector loop stopped
|
||||
/// (ill-formed UTF-8, or fewer than 16 bytes left): one byte or sequence at a
|
||||
|
||||
@@ -108,9 +108,6 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci
|
||||
{
|
||||
JSON_ASSERT(m_object != nullptr);
|
||||
|
||||
// m_it is already value-initialized, so this is not needed for
|
||||
// correctness; it is kept because GCC generates faster loops with it
|
||||
// (iterating a parsed document is 0.5-2.7 % slower without it, #6005)
|
||||
if (m_object->is_object())
|
||||
{
|
||||
m_it.object_iterator = typename object_t::iterator();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // copy
|
||||
#include <algorithm> // all_of, copy
|
||||
#include <array> // array
|
||||
#include <cctype> // isdigit
|
||||
#include <cerrno> // errno, ERANGE
|
||||
@@ -16,11 +16,9 @@
|
||||
#include <cstdint> // uint64_t
|
||||
#include <cstdlib> // strtoull
|
||||
#include <cstring> // memchr, memcmp, memcpy
|
||||
#include <functional> // less
|
||||
#ifndef JSON_NO_IO
|
||||
#include <iosfwd> // ostream
|
||||
#endif // JSON_NO_IO
|
||||
#include <iterator> // next
|
||||
#include <limits> // max
|
||||
#include <string> // string
|
||||
#include <utility> // move
|
||||
@@ -30,7 +28,6 @@
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/meta/void_t.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_escape.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
@@ -137,26 +134,6 @@ JSON_HEDLEY_ALWAYS_INLINE array_index_status parse_array_index(const StringType&
|
||||
return array_index_status::ok;
|
||||
}
|
||||
|
||||
/// whether two keys of type @a StringType are equivalent under @a Compare
|
||||
/// exactly if they are equal: std::less<StringType> and std::less<>
|
||||
template<typename Compare, typename StringType>
|
||||
struct is_less_comparator : std::false_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<StringType>, StringType> : std::true_type {};
|
||||
|
||||
template<typename StringType>
|
||||
struct is_less_comparator<std::less<void>, StringType> : std::true_type {};
|
||||
|
||||
/// whether the objects of type @a ObjectType find a key of type @a StringType
|
||||
/// exactly if it equals a stored key
|
||||
template<typename ObjectType, typename StringType, typename = void>
|
||||
struct finds_equal_keys : std::false_type {};
|
||||
|
||||
template<typename ObjectType, typename StringType>
|
||||
struct finds_equal_keys<ObjectType, StringType, void_t<typename ObjectType::key_compare, typename ObjectType::key_type>>
|
||||
: conjunction<is_less_comparator<typename ObjectType::key_compare, StringType>, std::is_same<typename ObjectType::key_type, StringType>> {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
|
||||
@@ -266,13 +243,6 @@ class json_pointer
|
||||
/// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/
|
||||
json_pointer& operator/=(const json_pointer& ptr)
|
||||
{
|
||||
// insert() must not be given iterators into the vector it inserts
|
||||
// into: append a copy when a pointer is appended to itself
|
||||
if (JSON_HEDLEY_UNLIKELY(&ptr == this))
|
||||
{
|
||||
return *this /= json_pointer(ptr);
|
||||
}
|
||||
|
||||
reference_tokens.insert(reference_tokens.end(),
|
||||
ptr.reference_tokens.begin(),
|
||||
ptr.reference_tokens.end());
|
||||
@@ -304,7 +274,7 @@ class json_pointer
|
||||
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;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @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/
|
||||
@@ -315,7 +285,7 @@ class json_pointer
|
||||
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;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @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/
|
||||
@@ -619,225 +589,6 @@ class json_pointer
|
||||
return object_member_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief append a null value to @a array, like operator[] with the size of @a array
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& array_append(BasicJsonType& array)
|
||||
{
|
||||
auto& elements = *array.m_data.m_value.array;
|
||||
const auto old_capacity = array.container_capacity();
|
||||
elements.emplace_back();
|
||||
return array.set_parent_after_insert(elements.back(), old_capacity);
|
||||
}
|
||||
|
||||
/// @brief add @a key, which is missing, to @a object
|
||||
template<typename BasicJsonType>
|
||||
static auto object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace(key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto it = object.m_data.m_value.object->emplace(key, nullptr).first;
|
||||
return object.set_parent_after_insert(it->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add_impl(BasicJsonType& object, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object.operator[](key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& object_add(BasicJsonType& object, const string_t& key)
|
||||
{
|
||||
return object_add_impl(object, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief search @a key in @a members: the result is where the key is or,
|
||||
/// for objects that can insert at a position (std::map), where it belongs
|
||||
template<typename ObjectType>
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, members.lower_bound(key)->first), members.lower_bound(key))
|
||||
{
|
||||
return members.lower_bound(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_search_impl(ObjectType& members, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
-> decltype(members.find(key))
|
||||
{
|
||||
return members.find(key);
|
||||
}
|
||||
|
||||
template<typename ObjectType>
|
||||
static auto object_search(ObjectType& members, const string_t& key)
|
||||
-> decltype(object_search_impl(members, key, detail::priority_tag<1> {}))
|
||||
{
|
||||
return object_search_impl(members, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/// @brief whether @a it, the result of @ref object_search for @a key, points to @a key
|
||||
///
|
||||
/// With std::less, the key at the lower bound is equivalent to @a key
|
||||
/// exactly if it is equal to it, and comparing for equality is cheaper.
|
||||
template<typename ObjectType, typename Iterator,
|
||||
detail::enable_if_t<detail::finds_equal_keys<ObjectType, string_t>::value, int> = 0>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<2> /*unused*/)
|
||||
-> decltype(members.lower_bound(key), it->first == key)
|
||||
{
|
||||
return it != members.end() && it->first == key;
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static auto object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(members.key_comp()(key, it->first), members.lower_bound(key), bool())
|
||||
{
|
||||
return it != members.end() && !members.key_comp()(key, it->first);
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found_impl(ObjectType& members, const Iterator& it, const string_t& /*key*/, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return it != members.end();
|
||||
}
|
||||
|
||||
template<typename ObjectType, typename Iterator>
|
||||
static bool object_search_found(ObjectType& members, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_search_found_impl(members, it, key, detail::priority_tag<2> {});
|
||||
}
|
||||
|
||||
/// @brief insert @a key, which is missing, into @a object at @a it, the
|
||||
/// result of @ref object_search for @a key
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static auto object_insert_impl(BasicJsonType& object, const Iterator& it, const string_t& key, detail::priority_tag<1> /*unused*/)
|
||||
-> decltype(object.m_data.m_value.object->emplace_hint(it, key, nullptr), std::declval<BasicJsonType&>())
|
||||
{
|
||||
const auto old_capacity = object.container_capacity();
|
||||
const auto pos = object.m_data.m_value.object->emplace_hint(it, key, nullptr);
|
||||
return object.set_parent_after_insert(pos->second, old_capacity);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert_impl(BasicJsonType& object, const Iterator& /*it*/, const string_t& key, detail::priority_tag<0> /*unused*/)
|
||||
{
|
||||
return object_add(object, key);
|
||||
}
|
||||
|
||||
template<typename BasicJsonType, typename Iterator>
|
||||
static BasicJsonType& object_insert(BasicJsonType& object, const Iterator& it, const string_t& key)
|
||||
{
|
||||
return object_insert_impl(object, it, key, detail::priority_tag<1> {});
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief undoes the change @ref get_unchecked_create made to an existing value
|
||||
|
||||
@ref get_unchecked walks existing values without changing them. At the
|
||||
first reference token that does not select an existing value, @ref
|
||||
get_unchecked_create makes exactly one change to an existing value: it
|
||||
converts a null value, inserts a key into an object, or grows an array.
|
||||
Everything it creates afterwards lies below that change. If a later step
|
||||
throws, the destructor of this guard reverts that one change, which
|
||||
removes everything created after it, so the value is left as it was
|
||||
(strong exception safety). The guard needs no try/catch and therefore also
|
||||
compiles with JSON_NOEXCEPTION.
|
||||
|
||||
Reverting the change must not throw, as it happens in a destructor. It
|
||||
erases the inserted member by its iterator (no key is converted or
|
||||
compared, nothing is allocated), erases the added elements at the end of
|
||||
the array, or swaps the null value back. This holds for std::map,
|
||||
std::vector, ordered_map, and every object_t, array_t, and
|
||||
json_base_class_t whose erase() and move operations do not throw.
|
||||
Between recording and reverting, nothing is inserted into or erased from
|
||||
the object or array, so the iterator stays valid.
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
class get_unchecked_rollback
|
||||
{
|
||||
public:
|
||||
using object_iterator = typename BasicJsonType::object_t::iterator;
|
||||
|
||||
/// record that @a member was inserted into the object @a value
|
||||
get_unchecked_rollback(BasicJsonType* value, const object_iterator& member)
|
||||
: m_value(value)
|
||||
, m_member(member)
|
||||
, m_change(change::key_inserted)
|
||||
{}
|
||||
|
||||
/// record the change that the next reference token makes to the null value or array @a value
|
||||
explicit get_unchecked_rollback(BasicJsonType* value)
|
||||
: m_value(value)
|
||||
{
|
||||
if (value->is_null())
|
||||
{
|
||||
// keep the null value (with its base class and positions) and
|
||||
// continue with a fresh null value in its place, which is what
|
||||
// assigning an array or object to it would have left there
|
||||
m_saved_null.swap(*value);
|
||||
m_change = change::null_converted;
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(value->is_array());
|
||||
m_old_size = value->m_data.m_value.array->size();
|
||||
m_change = change::array_grown;
|
||||
}
|
||||
}
|
||||
|
||||
get_unchecked_rollback(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback(get_unchecked_rollback&&) = delete;
|
||||
get_unchecked_rollback& operator=(const get_unchecked_rollback&) = delete;
|
||||
get_unchecked_rollback& operator=(get_unchecked_rollback&&) = delete;
|
||||
|
||||
~get_unchecked_rollback()
|
||||
{
|
||||
undo();
|
||||
}
|
||||
|
||||
/// keep the change
|
||||
void commit() noexcept
|
||||
{
|
||||
m_change = change::none;
|
||||
}
|
||||
|
||||
private:
|
||||
void undo() noexcept
|
||||
{
|
||||
switch (m_change)
|
||||
{
|
||||
case change::null_converted:
|
||||
// the values created since go away with m_saved_null
|
||||
m_value->swap(m_saved_null);
|
||||
break;
|
||||
case change::key_inserted:
|
||||
m_value->m_data.m_value.object->erase(m_member);
|
||||
break;
|
||||
case change::array_grown:
|
||||
{
|
||||
auto& elements = *m_value->m_data.m_value.array;
|
||||
elements.resize(m_old_size);
|
||||
break;
|
||||
}
|
||||
case change::none:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enum class change
|
||||
{
|
||||
none,
|
||||
null_converted,
|
||||
key_inserted,
|
||||
array_grown
|
||||
};
|
||||
|
||||
BasicJsonType* m_value;
|
||||
object_iterator m_member {};
|
||||
typename BasicJsonType::size_type m_old_size = 0;
|
||||
change m_change = change::none;
|
||||
BasicJsonType m_saved_null {};
|
||||
};
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
json_pointer top() const
|
||||
{
|
||||
@@ -1221,245 +972,47 @@ class json_pointer
|
||||
|
||||
Complexity: Linear in the length of the JSON pointer.
|
||||
|
||||
Exception safety: Strong guarantee. This function walks existing values
|
||||
until the first reference token that does not select one. If that is the
|
||||
last reference token, inserting the key into the object or growing the
|
||||
array is strongly exception safe by itself; any other change is made by
|
||||
@ref get_unchecked_create, which undoes it if a later step throws.
|
||||
|
||||
@throw parse_error.106 if an array index of an existing array begins with '0'
|
||||
@throw parse_error.109 if an array index of an existing array was not a number
|
||||
@throw out_of_range.404 if the JSON pointer can not be resolved, or if an
|
||||
array index is too large for strtoull
|
||||
@throw out_of_range.410 if an array index exceeds size_type
|
||||
@throw std::length_error if padding an array exceeds its maximal size
|
||||
@throw std::bad_alloc if an allocation fails
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@throw out_of_range.404 if the JSON pointer can not be resolved
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
BasicJsonType& get_unchecked(BasicJsonType* ptr) const
|
||||
{
|
||||
const auto* token = reference_tokens.data();
|
||||
const auto* const end = token + reference_tokens.size();
|
||||
if (token == end)
|
||||
for (const auto& reference_token : reference_tokens)
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// the null value is converted to an array or an object
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
// walk existing values; the walk itself inserts nothing, which keeps
|
||||
// the loop fast (insertions are done below or in get_unchecked_create)
|
||||
typename BasicJsonType::object_t::iterator it;
|
||||
do
|
||||
{
|
||||
if (ptr->is_object())
|
||||
{
|
||||
auto& members = *ptr->m_data.m_value.object;
|
||||
it = object_search(members, *token);
|
||||
if (!object_search_found(members, it, *token))
|
||||
{
|
||||
// the key is inserted below or in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (is_dash(*token))
|
||||
{
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
return array_append(*ptr);
|
||||
}
|
||||
// an element will be appended in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
if (token + 1 == end)
|
||||
{
|
||||
// convert array index to number; unchecked access
|
||||
return ptr->operator[](array_index<BasicJsonType>(*token));
|
||||
}
|
||||
const auto idx = array_index<BasicJsonType>(*token);
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
// the array will be padded in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else if (ptr->is_null())
|
||||
{
|
||||
// the null value will be converted in get_unchecked_create
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(*token, ptr);
|
||||
}
|
||||
}
|
||||
while (++token != end);
|
||||
|
||||
if (token == end)
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
if (token + 1 == end && ptr->is_object())
|
||||
{
|
||||
// insert the missing key where the walk found it belongs
|
||||
return object_insert(*ptr, it, *token);
|
||||
}
|
||||
return get_unchecked_create(ptr, token, end);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief create the values for the reference tokens from @a first on
|
||||
|
||||
Continues @ref get_unchecked at the first reference token that does not
|
||||
select an existing value. That token changes @a ptr, an existing value (see
|
||||
@ref get_unchecked_rollback); all later tokens only change values created
|
||||
here.
|
||||
|
||||
If the emplace() of the object type finds a member that the lookup in @ref
|
||||
get_unchecked did not find (the two disagree, e.g. for fifo_map), nothing
|
||||
is inserted, and the walk continues with that member as in @ref
|
||||
get_unchecked, which is what basic_json::operator[] does.
|
||||
|
||||
@param[in] ptr the value the reference token @a first is applied to
|
||||
@param[in] first the first reference token to resolve (an element of
|
||||
@ref reference_tokens)
|
||||
@param[in] end the end of @ref reference_tokens
|
||||
|
||||
@throw parse_error.106 if an array index of an existing array begins with
|
||||
'0' (only after emplace() found a member, see above)
|
||||
@throw parse_error.109 if an array index of an existing array was not a
|
||||
number (only after emplace() found a member)
|
||||
@throw out_of_range.404 if an array index is too large for strtoull, or (only
|
||||
after emplace() found a member) if the JSON pointer
|
||||
can not be resolved
|
||||
@throw out_of_range.410 if an array index exceeds size_type
|
||||
@throw std::length_error if padding an array exceeds its maximal size
|
||||
@throw std::bad_alloc if an allocation fails
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
JSON_HEDLEY_NEVER_INLINE static BasicJsonType& get_unchecked_create(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
const auto* token = first;
|
||||
while (ptr->is_object())
|
||||
{
|
||||
const auto old_capacity = ptr->container_capacity();
|
||||
const auto inserted = ptr->m_data.m_value.object->emplace(*token, nullptr);
|
||||
if (JSON_HEDLEY_LIKELY(inserted.second))
|
||||
{
|
||||
get_unchecked_rollback<BasicJsonType> rollback(ptr, inserted.first);
|
||||
BasicJsonType& result = create_values(&ptr->set_parent_after_insert(inserted.first->second, old_capacity), token + 1, end);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
// nothing was inserted: walk existing values from the member
|
||||
// emplace() found on, like get_unchecked
|
||||
ptr = &inserted.first->second;
|
||||
while (++token != end)
|
||||
{
|
||||
if (ptr->is_object())
|
||||
{
|
||||
auto& members = *ptr->m_data.m_value.object;
|
||||
const auto it = object_search(members, *token);
|
||||
if (!object_search_found(members, it, *token))
|
||||
{
|
||||
break;
|
||||
}
|
||||
ptr = &it->second;
|
||||
}
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
if (is_dash(*token))
|
||||
{
|
||||
break;
|
||||
}
|
||||
const auto idx = array_index<BasicJsonType>(*token);
|
||||
if (idx >= ptr->m_data.m_value.array->size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
ptr = &(*ptr->m_data.m_value.array)[idx];
|
||||
}
|
||||
else if (ptr->is_null())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(*token, ptr);
|
||||
}
|
||||
}
|
||||
if (token == end)
|
||||
{
|
||||
return *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
get_unchecked_rollback<BasicJsonType> rollback(ptr);
|
||||
BasicJsonType& result = create_values(ptr, token, end);
|
||||
rollback.commit();
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the loop of @ref get_unchecked_create
|
||||
|
||||
@a ptr is a null value or an array. Every value created here is a fresh
|
||||
one, so every object a key is added to is empty.
|
||||
|
||||
@throw out_of_range.404 if an array index is too large for strtoull
|
||||
@throw out_of_range.410 if an array index exceeds size_type
|
||||
@throw std::length_error if padding an array exceeds its maximal size
|
||||
@throw std::bad_alloc if an allocation fails
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
static BasicJsonType& create_values(BasicJsonType* ptr, const string_t* first, const string_t* end)
|
||||
{
|
||||
for (const auto* token = first; token != end; ++token)
|
||||
{
|
||||
const auto& reference_token = *token;
|
||||
|
||||
// convert null values to arrays or objects before continuing
|
||||
if (ptr->is_null())
|
||||
{
|
||||
// change value to an array for array indices or "-" or to an
|
||||
// object otherwise; every null value here is a fresh one (see
|
||||
// get_unchecked_rollback), so converting it in place leaves
|
||||
// the same value as assigning an array or object to it
|
||||
if (is_array_index_token(reference_token) || is_dash(reference_token))
|
||||
// check if the reference token is a valid array index, that is
|
||||
// a nonempty sequence of digits without a leading '0'
|
||||
// (cf. RFC 6901, Sect. 4); tokens that could never be a valid
|
||||
// array index (such as "01" or "") are treated as object keys
|
||||
const bool nums = !reference_token.empty()
|
||||
&& (reference_token.size() == 1 || reference_token[0] != '0')
|
||||
&& std::all_of(reference_token.begin(), reference_token.end(),
|
||||
[](const unsigned char x)
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::array_t>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ptr->template convert_null_to<typename BasicJsonType::object_t>();
|
||||
}
|
||||
return std::isdigit(x);
|
||||
});
|
||||
|
||||
// change value to an array for array indices or "-" or to object otherwise
|
||||
*ptr = (nums || reference_token == "-")
|
||||
? detail::value_t::array
|
||||
: detail::value_t::object;
|
||||
}
|
||||
|
||||
if (ptr->is_object())
|
||||
{
|
||||
// the object is new and empty
|
||||
ptr = &object_add(*ptr, reference_token);
|
||||
// use unchecked object access
|
||||
ptr = &object_member(*ptr, reference_token);
|
||||
}
|
||||
else
|
||||
else if (ptr->is_array())
|
||||
{
|
||||
// only the first value is not created here, and it is a null
|
||||
// value or an array (see get_unchecked_create)
|
||||
JSON_ASSERT(ptr->is_array());
|
||||
if (is_dash(reference_token))
|
||||
if (reference_token == "-")
|
||||
{
|
||||
// explicitly treat "-" as index beyond the end
|
||||
ptr = &array_append(*ptr);
|
||||
ptr = &ptr->operator[](ptr->m_data.m_value.array->size());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1467,31 +1020,16 @@ class json_pointer
|
||||
ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// primitive value with a reference token left
|
||||
throw_unresolved(reference_token, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
return *ptr;
|
||||
}
|
||||
|
||||
/// whether @a s is a valid array index, that is a nonempty sequence of
|
||||
/// digits without a leading '0' (cf. RFC 6901, Sect. 4); tokens that could
|
||||
/// never be a valid array index (such as "01" or "") are object keys
|
||||
static bool is_array_index_token(const string_t& s)
|
||||
{
|
||||
const std::size_t size = s.size();
|
||||
if (size == 0 || (size > 1 && s[0] == '0'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (std::isdigit(static_cast<unsigned char>(s[i])) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@throw parse_error.106 if an array index begins with '0'
|
||||
@throw parse_error.109 if an array index was not a number
|
||||
@@ -2180,7 +1718,7 @@ class json_pointer
|
||||
{
|
||||
// create an entry in the array
|
||||
current = ¤t->operator[](array_index<BasicJsonType>(token));
|
||||
break; // LCOV_EXCL_LINE (needs an array index with 10 or more digits: more than 1e9 elements)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2217,7 +1755,7 @@ class json_pointer
|
||||
json_pointer<string_t> result;
|
||||
result.reference_tokens = reference_tokens;
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
json_pointer<string_t> convert()&&
|
||||
{
|
||||
|
||||
@@ -347,7 +347,7 @@
|
||||
return ej_pair.second == j; \
|
||||
}); \
|
||||
if (it != std::end(m)) e = it->first; \
|
||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat(nlohmann::detail::exception_prefix(j), "enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||
else ::nlohmann::detail::templated_json_throw<nlohmann::detail::out_of_range>(nlohmann::detail::out_of_range::create(nlohmann::detail::exception_id::value_out_of_range, nlohmann::detail::concat("enum value out of range for " #ENUM_TYPE ": ", nlohmann::detail::from_json_dump(j)), nlohmann::detail::exception_context(j))); \
|
||||
}
|
||||
|
||||
// Ugly macros to avoid uglier copy-paste when specializing basic_json. They
|
||||
|
||||
@@ -833,7 +833,7 @@ class binary_writer
|
||||
while (n != 0);
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the decimal digits of the integer @a n, with a leading '-' if it
|
||||
@@ -860,7 +860,7 @@ class binary_writer
|
||||
}
|
||||
std::reverse(result.begin(), result.end());
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief throws because the integer @a j is too large for @a format
|
||||
@@ -1792,7 +1792,7 @@ class binary_writer
|
||||
std::memcpy(out, &n, sizeof(NumberType));
|
||||
if (!is_little_endian)
|
||||
{
|
||||
std::reverse(out, out + sizeof(NumberType)); // LCOV_EXCL_LINE (big-endian hosts only)
|
||||
std::reverse(out, out + sizeof(NumberType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1921,7 +1921,7 @@ class binary_writer
|
||||
const string_t& value = *j.m_data.m_value.string;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(value.size() + 1ul)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a string of 2 GiB or more)
|
||||
return false;
|
||||
}
|
||||
out[0] = to_char_type(0x02);
|
||||
store_little_endian<std::int32_t>(p, static_cast<std::int32_t>(value.size() + 1ul));
|
||||
@@ -2088,8 +2088,7 @@ class binary_writer
|
||||
/// arrays: the first index with one more digit; only maintained by
|
||||
/// @ref calc_bson_sizes
|
||||
std::uint64_t index_boundary = 10;
|
||||
/// @ref calc_bson_sizes: where its size goes in the table; @ref
|
||||
/// write_bson_document_single_pass: the position of its size field
|
||||
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||
std::size_t size_slot;
|
||||
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||
std::size_t entries_size = 0;
|
||||
@@ -2340,198 +2339,6 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SinkType>
|
||||
using sink_written_t = decltype(std::declval<const SinkType&>().written());
|
||||
|
||||
/// whether every integer fits the 64 bits BSON has, so that writing an
|
||||
/// array of numbers needs no range check
|
||||
static constexpr bool bson_integers_fit_64_bits =
|
||||
std::numeric_limits<typename BasicJsonType::number_integer_t>::is_specialized
|
||||
&& std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits
|
||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::is_specialized
|
||||
&& std::numeric_limits<typename BasicJsonType::number_unsigned_t>::digits <= std::numeric_limits<std::uint64_t>::digits;
|
||||
|
||||
/*!
|
||||
@brief whether BSON documents can be written in a single pass: the sink
|
||||
can overwrite what it has written (output_vector_sink), and
|
||||
@ref bson_integers_fit_64_bits
|
||||
*/
|
||||
using bson_single_pass_sink = conjunction<is_detected<sink_written_t, OutputSinkType>, std::integral_constant<bool, bson_integers_fit_64_bits>>;
|
||||
|
||||
/// write a placeholder for the size of an object or array whose entries
|
||||
/// follow; @return its position, for @ref close_bson_container
|
||||
std::size_t write_bson_size_placeholder()
|
||||
{
|
||||
const std::size_t pos = oa.written();
|
||||
// zero in any byte order
|
||||
const std::array<CharType, sizeof(std::int32_t)> zero{};
|
||||
oa.write_characters(zero.data(), zero.size());
|
||||
return pos;
|
||||
}
|
||||
|
||||
/// end the object or array whose size is at @a pos and write its size
|
||||
/// there: everything from that field to the terminating null byte
|
||||
/// @return false (and the size is not written) if it does not fit
|
||||
bool close_bson_container(const std::size_t pos)
|
||||
{
|
||||
oa.write_character(to_char_type(0x00));
|
||||
const std::size_t size = oa.written() - pos;
|
||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
const auto le_size = static_cast<std::uint32_t>(size);
|
||||
oa.overwrite(pos, is_little_endian ? le_size : byteswap(le_size));
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check the value @a j of a BSON entry as @ref calc_bson_value_size
|
||||
does, for @ref write_bson_document_single_pass
|
||||
@return at least the size of the value
|
||||
A number, boolean, or null needs no check there (its integers have at most
|
||||
64 bits) and has at most 8 characters; a discarded value is rejected with
|
||||
the same exception when it is written.
|
||||
*/
|
||||
std::size_t check_bson_value(const BasicJsonType& j)
|
||||
{
|
||||
return (j.is_string() || j.is_binary()) ? calc_bson_value_size(j) : 8;
|
||||
}
|
||||
|
||||
/// whether an entry of @a entry_size characters keeps the document that
|
||||
/// begins at @a start, closed by @a open_containers null bytes, within
|
||||
/// the 32-bit size of BSON
|
||||
bool bson_entry_fits(const std::size_t start, const std::size_t entry_size, const std::size_t open_containers) const noexcept
|
||||
{
|
||||
const auto limit = static_cast<std::size_t>((std::numeric_limits<std::int32_t>::max)());
|
||||
const std::size_t written = oa.written() - start;
|
||||
return written <= limit && entry_size <= limit - written && open_containers <= limit - written - entry_size;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document in a
|
||||
single pass, writing the size of each object and array once it is
|
||||
complete
|
||||
|
||||
Checks every key and value as @ref calc_bson_sizes does, in the same
|
||||
order, so the same exception is thrown; the output written up to then is
|
||||
discarded with the vector it was written to.
|
||||
|
||||
@return false as soon as the document or a string, binary value, object,
|
||||
or array in it would be larger than the 32-bit BSON sizes allow;
|
||||
nothing large has been written then, and the caller writes the
|
||||
document in two passes, which reports that error
|
||||
*/
|
||||
bool write_bson_document_single_pass(const BasicJsonType& document)
|
||||
{
|
||||
const std::size_t start = oa.written();
|
||||
// the object or array whose entries are being written (with the
|
||||
// position of its size), and the ones it is in
|
||||
bson_frame current(&document, write_bson_size_placeholder());
|
||||
std::vector<bson_frame> parents;
|
||||
|
||||
while (true)
|
||||
{
|
||||
const BasicJsonType* nested = nullptr;
|
||||
std::size_t nested_size_pos = 0;
|
||||
if (current.value->is_object())
|
||||
{
|
||||
const auto& object = *current.value->m_data.m_value.object;
|
||||
while (nested == nullptr && current.member != object.cend())
|
||||
{
|
||||
const auto& el = *current.member;
|
||||
++current.member;
|
||||
const std::size_t header_size = calc_bson_entry_header_size(el.first, el.second);
|
||||
const std::size_t value_size = el.second.is_structured() ? 0 : check_bson_value(el.second);
|
||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, header_size + value_size, parents.size() + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (el.second.is_structured())
|
||||
{
|
||||
write_bson_entry_header(el.first, el.second.is_object() ? 0x03 : 0x04);
|
||||
nested_size_pos = write_bson_size_placeholder();
|
||||
nested = &el.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
write_bson_value(el.first, el.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto& array = *current.value->m_data.m_value.array;
|
||||
while (nested == nullptr && current.index < array.size())
|
||||
{
|
||||
if (is_bson_fixed_size(array[current.index]))
|
||||
{
|
||||
write_bson_array_fixed_entries(current);
|
||||
if (current.index == array.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// a string, binary value, object or array
|
||||
const BasicJsonType& el = array[current.index];
|
||||
const string_t index_name(current.index_name.data(), current.index_name_size);
|
||||
current.next_index();
|
||||
const std::size_t value_size = el.is_structured() ? 0 : check_bson_value(el);
|
||||
if (JSON_HEDLEY_UNLIKELY(!bson_entry_fits(start, 2 + index_name.size() + value_size, parents.size() + 1)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (el.is_structured())
|
||||
{
|
||||
write_bson_entry_header(index_name, el.is_object() ? 0x03 : 0x04);
|
||||
nested_size_pos = write_bson_size_placeholder();
|
||||
nested = ⪙
|
||||
}
|
||||
else
|
||||
{
|
||||
write_bson_value(index_name, el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nested != nullptr)
|
||||
{
|
||||
if (nested->is_array())
|
||||
{
|
||||
// an array of booleans, numbers and null is written without a frame on the stack
|
||||
bson_frame leaf(nested, nested_size_pos);
|
||||
write_bson_array_fixed_entries(leaf);
|
||||
if (leaf.index == nested->m_data.m_value.array->size())
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(nested_size_pos)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
parents.push_back(std::move(current));
|
||||
current = std::move(leaf);
|
||||
continue;
|
||||
}
|
||||
parents.push_back(std::move(current));
|
||||
current = bson_frame(nested, nested_size_pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(!close_bson_container(current.size_slot)))
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (a document of 2 GiB or more)
|
||||
}
|
||||
if (parents.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
current = std::move(parents.back());
|
||||
parents.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document
|
||||
|
||||
@@ -2543,39 +2350,6 @@ class binary_writer
|
||||
@pre document.type() == value_t::object
|
||||
*/
|
||||
void write_bson_document(const BasicJsonType& document)
|
||||
{
|
||||
write_bson_document(document, bson_single_pass_sink());
|
||||
}
|
||||
|
||||
/// the sink can overwrite: one pass, unless the error handler replaces or
|
||||
/// drops ill-formed UTF-8, which changes the sizes the checks compute
|
||||
void write_bson_document(const BasicJsonType& document, std::true_type /*single_pass*/)
|
||||
{
|
||||
const std::size_t start = oa.written();
|
||||
if ((error_handler == error_handler_t::keep || error_handler == error_handler_t::strict)
|
||||
&& write_bson_document_single_pass(document))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// a length does not fit: start over, so that it is reported as the
|
||||
// two passes report it, before anything is written
|
||||
oa.rewind(start);
|
||||
write_bson_document_two_pass(document);
|
||||
}
|
||||
|
||||
void write_bson_document(const BasicJsonType& document, std::false_type /*single_pass*/)
|
||||
{
|
||||
write_bson_document_two_pass(document);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief Serializes the JSON object @a document as a BSON document: the
|
||||
sizes of all objects and arrays first, then the entries
|
||||
|
||||
@param[in] document the JSON object to serialize
|
||||
@pre document.type() == value_t::object
|
||||
*/
|
||||
void write_bson_document_two_pass(const BasicJsonType& document)
|
||||
{
|
||||
std::vector<std::size_t> nested_sizes;
|
||||
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||
@@ -2778,10 +2552,10 @@ class binary_writer
|
||||
|
||||
Selected through masks rather than branches, as the type and the sign of
|
||||
the values in an array of mixed signs alternate unpredictably (non-negative
|
||||
integers are number_unsigned after parsing). The sign is bit 63 of the
|
||||
result (a narrower integer is sign-extended); computing it with masks keeps
|
||||
GCC from branching on the type. Only called if @ref integers_fit_64_bits();
|
||||
a wider number type is written with range checks on the whole value.
|
||||
integers are number_unsigned after parsing). Up to 64 bits, the sign is bit
|
||||
63 of the result (a narrower integer is sign-extended); computing it with
|
||||
masks keeps GCC from branching on the type. A wider integer has to be
|
||||
compared.
|
||||
*/
|
||||
static std::uint64_t integer_bits(const BasicJsonType& j, std::uint64_t& sign_mask) noexcept
|
||||
{
|
||||
@@ -2789,7 +2563,9 @@ class binary_writer
|
||||
const std::uint64_t raw = is_signed
|
||||
? static_cast<std::uint64_t>(j.m_data.m_value.number_integer)
|
||||
: static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned);
|
||||
sign_mask = (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u));
|
||||
sign_mask = sizeof(typename BasicJsonType::number_integer_t) <= sizeof(std::uint64_t)
|
||||
? (std::uint64_t{0} - static_cast<std::uint64_t>(is_signed)) & (std::uint64_t{0} - (raw >> 63u))
|
||||
: std::uint64_t{0} - static_cast<std::uint64_t>(is_signed && j.m_data.m_value.number_integer < 0);
|
||||
return raw;
|
||||
}
|
||||
|
||||
@@ -3304,25 +3080,6 @@ class binary_writer
|
||||
}
|
||||
}
|
||||
|
||||
/// write the head and the characters of @a value, at most
|
||||
/// max_text_in_reservation of them, in one reservation
|
||||
JSON_HEDLEY_ALWAYS_INLINE void write_cbor_string_reserved(const string_t& value)
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 0x17)
|
||||
{
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0x60 + value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = to_char_type(0x78);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a CBOR text string
|
||||
|
||||
@@ -3344,7 +3101,19 @@ class binary_writer
|
||||
// the string is written as it is: head and characters in one reservation
|
||||
if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation))
|
||||
{
|
||||
write_cbor_string_reserved(value);
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 0x17)
|
||||
{
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0x60 + value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
*p++ = to_char_type(0x78);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3358,13 +3127,6 @@ class binary_writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_cbor_checked_string(const string_t& value, const BasicJsonType& context)
|
||||
{
|
||||
if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation)
|
||||
{
|
||||
check_utf8(value, context);
|
||||
write_cbor_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
|
||||
string_t storage;
|
||||
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||
|
||||
@@ -3379,27 +3141,6 @@ class binary_writer
|
||||
// MsgPack //
|
||||
/////////////
|
||||
|
||||
/// write the head and the characters of @a value, at most
|
||||
/// max_text_in_reservation of them, in one reservation
|
||||
JSON_HEDLEY_ALWAYS_INLINE void write_msgpack_string_reserved(const string_t& value)
|
||||
{
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 31)
|
||||
{
|
||||
// fixstr
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0xA0 | value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// str 8
|
||||
*p++ = to_char_type(0xD9);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a MessagePack str
|
||||
|
||||
@@ -3417,7 +3158,21 @@ class binary_writer
|
||||
// the string is written as it is: head and characters in one reservation
|
||||
if (JSON_HEDLEY_LIKELY(value.size() <= max_text_in_reservation))
|
||||
{
|
||||
write_msgpack_string_reserved(value);
|
||||
CharType* const out = oa.reserve(value.size() + 2);
|
||||
CharType* p = out;
|
||||
if (value.size() <= 31)
|
||||
{
|
||||
// fixstr
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(0xA0 | value.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// str 8
|
||||
*p++ = to_char_type(0xD9);
|
||||
*p++ = to_char_type(static_cast<std::uint8_t>(value.size()));
|
||||
}
|
||||
copy_characters(p, reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||
oa.commit(static_cast<std::size_t>(p - out) + value.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -3430,13 +3185,6 @@ class binary_writer
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
void write_msgpack_checked_string(const string_t& value, const BasicJsonType& context)
|
||||
{
|
||||
if (error_handler == error_handler_t::strict && value.size() <= max_text_in_reservation)
|
||||
{
|
||||
check_utf8(value, context);
|
||||
write_msgpack_string_reserved(value);
|
||||
return;
|
||||
}
|
||||
|
||||
string_t storage;
|
||||
const string_t& sanitized = sanitize_utf8_for_write(value, context, storage);
|
||||
|
||||
@@ -4156,34 +3904,57 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether the BJData ND-array element @a el can be written with dtype 'd' (single precision)
|
||||
@return whether @a el's value survives narrowing to float and back without any change
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN
|
||||
@brief validate (dry_run) or write one BJData ND-array element of integer dtype @a T
|
||||
@return whether @a el's value is in range of @a T; always true when @a dry_run is false
|
||||
*/
|
||||
static bool bjdata_ndarray_float_in_range(const BasicJsonType& el) noexcept
|
||||
template<typename T>
|
||||
bool write_bjdata_ndarray_element(const BasicJsonType& el, const bool dry_run)
|
||||
{
|
||||
if (dry_run)
|
||||
{
|
||||
return bjdata_ndarray_value_in_range<T>(el);
|
||||
}
|
||||
using storage_type = typename std::conditional<std::is_unsigned<T>::value, std::uint64_t, std::int64_t>::type;
|
||||
write_number(static_cast<T>(el.template get<storage_type>()), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief validate (dry_run) or write one BJData ND-array element of dtype 'd' (single precision)
|
||||
@return whether @a el's value survives narrowing to float and back without any change
|
||||
(so the ND-array round-trips exactly), or is infinite or NaN; always true when
|
||||
@a dry_run is false
|
||||
*/
|
||||
bool write_bjdata_ndarray_float_element(const BasicJsonType& el, const bool dry_run)
|
||||
{
|
||||
const auto dval = static_cast<double>(el.m_data.m_value.number_float);
|
||||
if (dry_run)
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
return std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
// a value that would be rounded (rather than exactly represented) by the
|
||||
// narrowing to float is treated like an out-of-range integer element; this
|
||||
// is the same criterion write_compact_float() uses for CBOR/MessagePack
|
||||
const bool in_range = std::isnan(dval) ||
|
||||
(dval >= static_cast<double>(std::numeric_limits<float>::lowest()) &&
|
||||
dval <= static_cast<double>((std::numeric_limits<float>::max)()) &&
|
||||
static_cast<double>(static_cast<float>(dval)) == dval) ||
|
||||
std::isinf(dval);
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
#endif
|
||||
return in_range;
|
||||
}
|
||||
write_number(static_cast<float>(dval), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write the elements of an ND-array as the number kind @a dtype names
|
||||
(see @ref write_bjdata_ndarray), after they have been found to be
|
||||
in range by @ref bjdata_ndarray_elements_in_range
|
||||
in range by a dry run of @ref write_bjdata_ndarray_elements
|
||||
|
||||
The elements are encoded in blocks into space reserved from the sink.
|
||||
*/
|
||||
@@ -4265,13 +4036,20 @@ class binary_writer
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief whether every element of a BJData ND-array's `_ArrayData_` is in range for its dtype
|
||||
@brief validate or write every element of a BJData ND-array's `_ArrayData_`
|
||||
@param[in] array_data the `_ArrayData_` array
|
||||
@param[in] dtype the ND-array dtype marker, as returned by bjdata_ndarray_type_marker()
|
||||
@return whether every element is in range for @a dtype
|
||||
@param[in] dry_run true to only range-check each element, false to write it
|
||||
@return whether every element is in range for @a dtype (always true when @a dry_run is false)
|
||||
*/
|
||||
static bool bjdata_ndarray_elements_in_range(const BasicJsonType& array_data, const CharType dtype)
|
||||
bool write_bjdata_ndarray_elements(const BasicJsonType& array_data, const CharType dtype, const bool dry_run)
|
||||
{
|
||||
if (!dry_run)
|
||||
{
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto& el : array_data)
|
||||
{
|
||||
bool ok = true;
|
||||
@@ -4279,39 +4057,43 @@ class binary_writer
|
||||
{
|
||||
case 'U':
|
||||
case 'B':
|
||||
ok = bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::uint8_t>(el, dry_run);
|
||||
break;
|
||||
case 'C':
|
||||
// BJData limits a char to 0..127 (the intersection of the int8 and uint8 ranges)
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el) && bjdata_ndarray_value_in_range<std::uint8_t>(el);
|
||||
break;
|
||||
case 'i':
|
||||
ok = bjdata_ndarray_value_in_range<std::int8_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::int8_t>(el, dry_run);
|
||||
break;
|
||||
case 'u':
|
||||
ok = bjdata_ndarray_value_in_range<std::uint16_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::uint16_t>(el, dry_run);
|
||||
break;
|
||||
case 'I':
|
||||
ok = bjdata_ndarray_value_in_range<std::int16_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::int16_t>(el, dry_run);
|
||||
break;
|
||||
case 'm':
|
||||
ok = bjdata_ndarray_value_in_range<std::uint32_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::uint32_t>(el, dry_run);
|
||||
break;
|
||||
case 'l':
|
||||
ok = bjdata_ndarray_value_in_range<std::int32_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::int32_t>(el, dry_run);
|
||||
break;
|
||||
case 'M':
|
||||
ok = bjdata_ndarray_value_in_range<std::uint64_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::uint64_t>(el, dry_run);
|
||||
break;
|
||||
case 'L':
|
||||
ok = bjdata_ndarray_value_in_range<std::int64_t>(el);
|
||||
ok = write_bjdata_ndarray_element<std::int64_t>(el, dry_run);
|
||||
break;
|
||||
case 'd':
|
||||
ok = bjdata_ndarray_float_in_range(el);
|
||||
ok = write_bjdata_ndarray_float_element(el, dry_run);
|
||||
break;
|
||||
case 'D':
|
||||
default:
|
||||
// 'D' (double) already spans the full range of number_float_t
|
||||
if (!dry_run)
|
||||
{
|
||||
write_number(el.template get<double>(), true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!ok)
|
||||
@@ -4398,7 +4180,7 @@ class binary_writer
|
||||
const auto dim = el.template get<std::uint64_t>();
|
||||
if (!value_in_range_of<std::size_t>(dim))
|
||||
{
|
||||
return true; // LCOV_EXCL_LINE (size_t is narrower than 64 bits)
|
||||
return true;
|
||||
}
|
||||
const auto dim_size = static_cast<std::size_t>(dim);
|
||||
|
||||
@@ -4448,7 +4230,7 @@ class binary_writer
|
||||
// wrap (integers) or overflow to infinity (the "single" precision
|
||||
// float) instead of being reported, so such an object falls back to
|
||||
// a plain object encoding as well
|
||||
if (!bjdata_ndarray_elements_in_range(array_data, dtype))
|
||||
if (!write_bjdata_ndarray_elements(array_data, dtype, true))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -4460,7 +4242,7 @@ class binary_writer
|
||||
|
||||
write_ubjson(array_size, use_count, use_type, true, true, bjdata_version);
|
||||
|
||||
write_bjdata_ndarray_values(array_data, dtype);
|
||||
write_bjdata_ndarray_elements(array_data, dtype, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4786,8 +4568,8 @@ class binary_writer
|
||||
run are encoded into space reserved from the sink, as in
|
||||
@ref write_array_elements; an integer that BON8 cannot write (an unsigned
|
||||
one above the int64 range, or one of a number type wider than 64 bits) throws
|
||||
out_of_range.407, as write_bon8_value does. Kept out of line, like
|
||||
@ref write_cbor_array.
|
||||
from write_bon8_value, like any other value that is not a number. Kept out
|
||||
of line, like @ref write_cbor_array.
|
||||
*/
|
||||
template<typename ArrayType>
|
||||
JSON_HEDLEY_NEVER_INLINE
|
||||
@@ -4837,10 +4619,10 @@ class binary_writer
|
||||
string_open = false;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(unencodable))
|
||||
if (unencodable)
|
||||
{
|
||||
// an integer beyond the int64 range, as write_bon8_value would report it
|
||||
throw_integer_out_of_range(*it, input_format_t::bon8, "int64");
|
||||
write_bon8_value(*it, string_open, depth + 1);
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4875,13 +4657,14 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_integer:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits<std::int64_t>(value.m_data.m_value.number_integer)))
|
||||
{
|
||||
// beyond 64 bits (number types wider than that)
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
break;
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_integer));
|
||||
string_open = false;
|
||||
// beyond 64 bits (number types wider than that): throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
break;
|
||||
|
||||
case value_t::number_float:
|
||||
@@ -4890,13 +4673,14 @@ class binary_writer
|
||||
break;
|
||||
|
||||
case value_t::number_unsigned:
|
||||
if (JSON_HEDLEY_UNLIKELY(!integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
if (JSON_HEDLEY_LIKELY(integer_fits_max<std::int64_t>(value.m_data.m_value.number_unsigned)))
|
||||
{
|
||||
// too large for BON8
|
||||
throw_integer_out_of_range(value, input_format_t::bon8, "int64");
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
break;
|
||||
}
|
||||
write_bon8_integer(static_cast<std::int64_t>(value.m_data.m_value.number_unsigned));
|
||||
string_open = false;
|
||||
// too large for BON8: throws
|
||||
write_bon8_value(value, string_open, depth + 1);
|
||||
break;
|
||||
|
||||
case value_t::object:
|
||||
@@ -5010,9 +4794,10 @@ class binary_writer
|
||||
static void check_utf8(const string_t& s, const BasicJsonType& context)
|
||||
{
|
||||
const auto* data = reinterpret_cast<const unsigned char*>(s.data());
|
||||
if (JSON_HEDLEY_UNLIKELY(!valid_utf8_string(data, s.size())))
|
||||
const std::size_t valid = valid_utf8_prefix(data, s.size());
|
||||
if (JSON_HEDLEY_UNLIKELY(valid != s.size()))
|
||||
{
|
||||
throw_invalid_utf8(data, valid_utf8_prefix(data, s.size()), context);
|
||||
throw_invalid_utf8(data, valid, context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint32_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
@@ -228,31 +227,6 @@ class output_vector_sink
|
||||
void flush() noexcept
|
||||
{}
|
||||
|
||||
/// the number of characters written so far, the position of the next one
|
||||
std::size_t written() const noexcept
|
||||
{
|
||||
return position();
|
||||
}
|
||||
|
||||
/// @brief replace the 4 bytes written before at position @a pos with
|
||||
/// those of @a n, in the byte order of the machine
|
||||
///
|
||||
/// Lets a writer put a length in front of data it has written since, such
|
||||
/// as the size of a BSON document, without computing it in advance.
|
||||
void overwrite(const std::size_t pos, const std::uint32_t n) noexcept
|
||||
{
|
||||
static_assert(sizeof(CharType) == 1, "overwrite() writes bytes");
|
||||
JSON_ASSERT(pos + sizeof(n) <= position());
|
||||
std::memcpy(v->data() + pos, &n, sizeof(n));
|
||||
}
|
||||
|
||||
/// drop the characters written from position @a pos on
|
||||
void rewind(const std::size_t pos) noexcept
|
||||
{
|
||||
JSON_ASSERT(pos <= position());
|
||||
cursor = v->data() + pos;
|
||||
}
|
||||
|
||||
private:
|
||||
/// number of characters of *v written so far
|
||||
std::size_t position() const noexcept
|
||||
|
||||
@@ -1113,21 +1113,36 @@ class serializer
|
||||
{
|
||||
// the code point is at least 0x60 (smaller ones are handled
|
||||
// above) and is not printable ASCII (which the scanner
|
||||
// copies). Under EnsureAscii it is DEL (0x7F): every
|
||||
// well-formed multi-byte character is escaped by
|
||||
// write_u_escapes() above, so decode() only ever accepts a
|
||||
// single byte here, and nothing of it was buffered
|
||||
// copies): it is DEL (0x7F) under EnsureAscii or a non-ASCII
|
||||
// character
|
||||
if (EnsureAscii)
|
||||
{
|
||||
JSON_ASSERT(codepoint == 0x7F);
|
||||
JSON_ASSERT(bytes == bytes_after_last_accept);
|
||||
write_u_escape(bytes, static_cast<std::uint16_t>(codepoint));
|
||||
JSON_ASSERT(codepoint >= 0x7F);
|
||||
if (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 of the
|
||||
// sequence have been copied in the default case below)
|
||||
string_buffer[bytes++] = s[i]; // LCOV_EXCL_LINE (well-formed sequences are copied in runs)
|
||||
string_buffer[bytes++] = s[i];
|
||||
}
|
||||
|
||||
reserve_string_buffer(bytes);
|
||||
@@ -1962,23 +1977,6 @@ class serializer
|
||||
return;
|
||||
}
|
||||
|
||||
// 17 to 20 digits: the first one to four, then twice eight
|
||||
if (magnitude_fits_64_bits(abs_value, std::integral_constant<bool, magnitude_has_at_most_64_bits>()))
|
||||
{
|
||||
const auto value = static_cast<std::uint64_t>(abs_value);
|
||||
const std::uint64_t top = value / 10000000000000000u; // 1 to 1844
|
||||
const std::uint64_t low16 = value - (top * 10000000000000000u);
|
||||
const std::uint64_t middle = low16 / 100000000u;
|
||||
const std::uint64_t first = dtoa_impl::eight_digit_bytes(top);
|
||||
const auto leading = static_cast<unsigned>(count_leading_zeros(first) / 8); // (first is not 0)
|
||||
char* const p = buffer_ptr + n_chars;
|
||||
dtoa_impl::store_msb_first(p, (first << (8 * leading)) + 0x3030303030303030u);
|
||||
dtoa_impl::store_msb_first(p + 8 - leading, dtoa_impl::eight_digit_bytes(middle) + 0x3030303030303030u);
|
||||
dtoa_impl::store_msb_first(p + 16 - leading, dtoa_impl::eight_digit_bytes(low16 - (middle * 100000000u)) + 0x3030303030303030u);
|
||||
write_buffer_pos += n_chars + 24 - leading;
|
||||
return;
|
||||
}
|
||||
|
||||
n_chars += count_digits(abs_value);
|
||||
|
||||
// spare 1 byte for '\0'
|
||||
@@ -2124,23 +2122,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/// whether every number_magnitude_t fits 64 bits
|
||||
static constexpr bool magnitude_has_at_most_64_bits = std::numeric_limits<number_magnitude_t>::is_specialized
|
||||
&& std::numeric_limits<number_magnitude_t>::digits <= 64;
|
||||
|
||||
/// whether @a x fits 64 bits: always if number_magnitude_t has at most
|
||||
/// 64 bits (selected by type, as the comparison would be reported as
|
||||
/// always true), else compared
|
||||
static bool magnitude_fits_64_bits(const number_magnitude_t& /*unused*/, std::true_type /*unused*/) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool magnitude_fits_64_bits(const number_magnitude_t& x, std::false_type /*unused*/) noexcept
|
||||
{
|
||||
return x <= 18446744073709551615u;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for dump_integer
|
||||
*
|
||||
|
||||
@@ -139,7 +139,7 @@ inline OutStringType concat(Args && ... args)
|
||||
str.reserve(concat_length(args...));
|
||||
concat_into(str, std::forward<Args>(args)...);
|
||||
return str;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -83,6 +83,10 @@ template<typename StringType>
|
||||
inline void unescape(StringType& s)
|
||||
{
|
||||
auto pos = s.find_first_of('~', 0);
|
||||
if (pos == StringType::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StringType result;
|
||||
result.reserve(s.size());
|
||||
|
||||
@@ -36,7 +36,7 @@ StringType to_string(std::size_t value)
|
||||
StringType result;
|
||||
int_to_string(result, value);
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @return a byte as two uppercase hexadecimal digits
|
||||
inline std::string hex_byte(const std::uint8_t byte)
|
||||
@@ -46,7 +46,7 @@ inline std::string hex_byte(const std::uint8_t byte)
|
||||
result[0] = nibble_to_hex[byte / 16];
|
||||
result[1] = nibble_to_hex[byte % 16];
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write a UTF-16 code unit as four lowercase hexadecimal digits
|
||||
@@ -390,7 +390,7 @@ inline StringType sanitize_utf8(const StringType& s, const error_handler_t error
|
||||
}
|
||||
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -69,8 +69,7 @@ struct parse_failure
|
||||
};
|
||||
|
||||
/// FloatType: the number_float_t of the document, whose overflow parse() rejects
|
||||
/// Store: false for accept(): the grammar, number, string and UTF-8 checks run as for parse(), but no node is written
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel, bool Store>
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Sentinel>
|
||||
class builder
|
||||
{
|
||||
public:
|
||||
@@ -174,16 +173,31 @@ class builder
|
||||
}
|
||||
if (p[1] == '/')
|
||||
{
|
||||
return skip_line_comment<NulIsEnd>(p + 2, m_end);
|
||||
p += 2;
|
||||
// (as in parse(), a null byte is the end of the input, so it is
|
||||
// left for the caller to see)
|
||||
while (p != m_end && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
if (p[1] == '*')
|
||||
{
|
||||
p = find_block_comment_end<NulIsEnd>(p + 2, m_end);
|
||||
if (p == m_end || *p != '*')
|
||||
p += 2;
|
||||
for (;;)
|
||||
{
|
||||
return fail_at(error_code::comment_unterminated, p);
|
||||
if (p == m_end || (NulIsEnd && *p == 0))
|
||||
{
|
||||
return fail_at(error_code::comment_unterminated, p);
|
||||
}
|
||||
if (*p == '*' && p + 1 != m_end && p[1] == '/')
|
||||
{
|
||||
p += 2;
|
||||
return p;
|
||||
}
|
||||
++p;
|
||||
}
|
||||
return p + 2;
|
||||
}
|
||||
++p;
|
||||
return fail_at(error_code::comment_start, p);
|
||||
@@ -246,87 +260,11 @@ class builder
|
||||
return p;
|
||||
}
|
||||
|
||||
/// the string from p, the first backslash, control character, ill-formed UTF-8 byte, or the end, checked and not
|
||||
/// decoded (accept() stores nothing, so the arena is not written): the checks, errors and error positions of
|
||||
/// slow_string(), for everything up to the closing quote
|
||||
NLOHMANN_VIEW_NOINLINE decoded check_string(const unsigned char* p)
|
||||
{
|
||||
const unsigned char* const end = m_end;
|
||||
for (;;)
|
||||
{
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
const unsigned char c = *p;
|
||||
if (c == '"')
|
||||
{
|
||||
++p;
|
||||
return decoded{p, 0, 0};
|
||||
}
|
||||
if (c != '\\')
|
||||
{
|
||||
return failed(c < 0x20 ? error_code::string_control_character : error_code::string_utf8, p);
|
||||
}
|
||||
++p;
|
||||
if (p == end)
|
||||
{
|
||||
return failed(error_code::string_missing_quote, p);
|
||||
}
|
||||
const unsigned char d = *p++;
|
||||
if (d == 'u')
|
||||
{
|
||||
const int cp = hex4(p); // (moves p past the four digits)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cp < 0))
|
||||
{
|
||||
return failed(error_code::string_unicode_hex, p);
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY((cp & 0xF800) == 0xD800)) // a surrogate
|
||||
{
|
||||
if (cp >= 0xDC00)
|
||||
{
|
||||
return failed(error_code::string_surrogate_low, p);
|
||||
}
|
||||
if (end - p < 2 || p[0] != '\\' || p[1] != 'u')
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
p += 2;
|
||||
const int lo = hex4(p); // (moves p past the four digits)
|
||||
if (lo < 0)
|
||||
{
|
||||
return failed(error_code::string_unicode_hex, p);
|
||||
}
|
||||
if (lo < 0xDC00 || lo > 0xDFFF)
|
||||
{
|
||||
return failed(error_code::string_surrogate_high, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!(d == '"' || d == '\\' || d == '/' || d == 'b' || d == 'f' || d == 'n' || d == 'r' || d == 't'))
|
||||
{
|
||||
--p;
|
||||
return failed(error_code::string_escape, p);
|
||||
}
|
||||
if (p != end && *p == '\\')
|
||||
{
|
||||
continue; // (escapes in a row: no run of text between them)
|
||||
}
|
||||
p = scan_string_run(p, end); // the text up to the next backslash, quote, control character, or ill-formed UTF-8
|
||||
}
|
||||
}
|
||||
|
||||
/// s points to the start of the string, p to a backslash, a control
|
||||
/// character, an ill-formed UTF-8 byte, or the end: decode the string into
|
||||
/// the arena
|
||||
NLOHMANN_VIEW_NOINLINE decoded slow_string(const unsigned char* s, const unsigned char* p)
|
||||
{
|
||||
if (!enabled(Store))
|
||||
{
|
||||
// (accept(): the string is checked, not decoded)
|
||||
static_cast<void>(s);
|
||||
return check_string(p);
|
||||
}
|
||||
// single-character escapes; 0: invalid (and 'u', handled separately)
|
||||
static const std::array<char, 128> simple_escape =
|
||||
{
|
||||
@@ -420,21 +358,6 @@ class builder
|
||||
}
|
||||
arena_ensure(w, w_end, 4);
|
||||
w = put_utf8(w, cp);
|
||||
// A chain of unicode escapes ("\u3042\u3044\u3046", as Python's json.dumps writes non-ASCII text) is
|
||||
// decoded here, without going round the loop above for each of them. Anything else (a surrogate, a
|
||||
// bad digit, fewer than six bytes) leaves p at the backslash: the loop above handles it, and reports
|
||||
// the same error at the same position as it would without this loop.
|
||||
while (end - p >= 6 && p[0] == '\\' && p[1] == 'u')
|
||||
{
|
||||
const int next = hex_codepoint(p + 2);
|
||||
if (next < 0 || (next & 0xF800) == 0xD800)
|
||||
{
|
||||
break;
|
||||
}
|
||||
p += 6;
|
||||
arena_ensure(w, w_end, 4);
|
||||
w = put_utf8(w, next);
|
||||
}
|
||||
}
|
||||
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
|
||||
{
|
||||
@@ -702,13 +625,10 @@ run_found:
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE bool run()
|
||||
{
|
||||
if (enabled(Store))
|
||||
{
|
||||
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
|
||||
base = cold.m_doc.nodes;
|
||||
out = base;
|
||||
cap = base + cold.m_doc.node_capacity;
|
||||
}
|
||||
cold.m_doc.reserve(estimate_nodes(reinterpret_cast<const char*>(b), static_cast<std::size_t>(e - b)));
|
||||
base = cold.m_doc.nodes;
|
||||
out = base;
|
||||
cap = base + cold.m_doc.node_capacity;
|
||||
|
||||
if (e - p >= 3 && p[0] == 0xEF && p[1] == 0xBB && p[2] == 0xBF)
|
||||
{
|
||||
@@ -892,10 +812,7 @@ close_object:
|
||||
// an array pays nothing for this)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(cur_count >= document_data::index_min_members))
|
||||
{
|
||||
if (enabled(Store))
|
||||
{
|
||||
cold.note_large_object(cur_idx);
|
||||
}
|
||||
cold.note_large_object(cur_idx);
|
||||
}
|
||||
|
||||
close_container:
|
||||
@@ -1055,12 +972,6 @@ indent_done:
|
||||
/// an integer's value); two stores on little-endian targets
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE node* push_node(node_kind k, std::uint8_t flags, std::uint16_t extra, std::size_t off, std::uint64_t second)
|
||||
{
|
||||
if (!enabled(Store))
|
||||
{
|
||||
// accept(): nothing is stored, so what computes the arguments is not run either. base and out stay
|
||||
// null: their difference, which open() takes, is 0.
|
||||
return out;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(out == cap))
|
||||
{
|
||||
const auto n = static_cast<std::size_t>(out - base);
|
||||
@@ -1119,12 +1030,9 @@ indent_done:
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void close()
|
||||
{
|
||||
if (enabled(Store))
|
||||
{
|
||||
node& n = base[cur_idx];
|
||||
n.len = cur_count;
|
||||
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
|
||||
}
|
||||
node& n = base[cur_idx];
|
||||
n.len = cur_count;
|
||||
n.next = static_cast<std::uint32_t>(out - base) - cur_idx;
|
||||
if (--depth != 0)
|
||||
{
|
||||
if (NLOHMANN_VIEW_LIKELY(depth <= 64))
|
||||
@@ -1174,9 +1082,8 @@ indent_done:
|
||||
{
|
||||
++p;
|
||||
}
|
||||
else if (!negative || NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
else if (NLOHMANN_VIEW_LIKELY(cur() >= '1' && cur() <= '9'))
|
||||
{
|
||||
// (without a sign, the dispatch saw a digit)
|
||||
p = skip_digits(p + 1, e);
|
||||
}
|
||||
else
|
||||
@@ -1190,7 +1097,7 @@ indent_done:
|
||||
{
|
||||
++p;
|
||||
const unsigned char* const f0 = p;
|
||||
p = skip_fraction_digits(p, e);
|
||||
p = skip_digits(p, e);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(p == f0))
|
||||
{
|
||||
return fail(error_code::number_after_dot);
|
||||
@@ -1286,58 +1193,41 @@ indent_done:
|
||||
return false;
|
||||
}
|
||||
p = r.p;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// the decoded string is in the arena: next keeps the source offset
|
||||
// of the opening quote plus one (0: unknown) for materialize()
|
||||
const std::uint64_t second = static_cast<std::uint64_t>(r.len) | (static_cast<std::uint64_t>(s - b) << 32);
|
||||
#else
|
||||
const std::uint64_t second = r.len;
|
||||
#endif
|
||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, second);
|
||||
push_node(node_kind::string, node_flags::escaped, 0, r.start, r.len);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// run the builder with compile-time options
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd, bool Store>
|
||||
template<typename FloatType, bool Comments, bool TrailingCommas, bool NulIsEnd>
|
||||
inline bool build_with(document_data& d, const char* src, std::size_t size, bool sentinel, parse_failure& failure)
|
||||
{
|
||||
if (sentinel)
|
||||
{
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true, Store> bld(d, src, size);
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, true> bld(d, src, size);
|
||||
const bool ok = bld.run();
|
||||
failure = bld.failure();
|
||||
return ok;
|
||||
}
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false, Store> bld(d, src, size);
|
||||
builder<FloatType, Comments, TrailingCommas, NulIsEnd, false> bld(d, src, size);
|
||||
const bool ok = bld.run();
|
||||
failure = bld.failure();
|
||||
return ok;
|
||||
}
|
||||
|
||||
/// sentinel: src[size] is readable and 0 (e.g. std::string); FloatType: the
|
||||
/// number_float_t of the document; validate_only: accept(), only the result is
|
||||
/// used
|
||||
/// number_float_t of the document
|
||||
template<typename FloatType, bool NulIsEnd>
|
||||
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, bool validate_only,
|
||||
parse_failure& failure)
|
||||
inline bool build(document_data& d, const char* src, std::size_t size, bool comments, bool trailing_commas, bool sentinel, parse_failure& failure)
|
||||
{
|
||||
if (validate_only && !comments && !trailing_commas)
|
||||
{
|
||||
// accept() of plain JSON: nothing is stored. The other option sets
|
||||
// could skip the stores just as well; they keep the builder that
|
||||
// stores only so that there are two more instantiations of the parse
|
||||
// loop, not eight.
|
||||
return build_with<FloatType, false, false, NulIsEnd, false>(d, src, size, sentinel, failure);
|
||||
}
|
||||
if (comments)
|
||||
{
|
||||
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd, true>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, true, false, NulIsEnd, true>(d, src, size, sentinel, failure);
|
||||
return trailing_commas ? build_with<FloatType, true, true, NulIsEnd>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, true, false, NulIsEnd>(d, src, size, sentinel, failure);
|
||||
}
|
||||
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd, true>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, false, false, NulIsEnd, true>(d, src, size, sentinel, failure);
|
||||
return trailing_commas ? build_with<FloatType, false, true, NulIsEnd>(d, src, size, sentinel, failure)
|
||||
: build_with<FloatType, false, false, NulIsEnd>(d, src, size, sentinel, failure);
|
||||
}
|
||||
|
||||
} // namespace view
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -26,7 +26,6 @@
|
||||
#include <nlohmann/detail/view/lookup.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
#include <nlohmann/detail/view/view_mode.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -47,9 +46,10 @@ struct first_true<false, Conditions...> : std::integral_constant < int, 1 + firs
|
||||
/// (type_error.316 with the same message), so that an editable document
|
||||
/// only holds valid UTF-8: the error is at the first byte that no
|
||||
/// well-formed sequence can continue with (Unicode, Table 3-7).
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE void check_utf8_bytewise(const unsigned char* p, std::size_t n, std::size_t i)
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
while (i < n)
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
for (std::size_t i = 0; i < n;)
|
||||
{
|
||||
const unsigned char c = p[i];
|
||||
if (c < 0x80)
|
||||
@@ -79,43 +79,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE void check_utf8_bytewise(const unsigned char* p, std
|
||||
}
|
||||
}
|
||||
|
||||
/// The long-string path of check_utf8(): skips well-formed runs with the
|
||||
/// parser's string scan (which stops at a quote, a backslash, or a control
|
||||
/// character, all legal here, and at ill-formed UTF-8); the byte-wise check
|
||||
/// runs only from where the scan found ill-formed UTF-8, to report it.
|
||||
NLOHMANN_VIEW_NOINLINE inline void check_utf8_scan(const unsigned char* p, std::size_t n)
|
||||
{
|
||||
const unsigned char* const e = p + n;
|
||||
const unsigned char* q = p;
|
||||
for (;;)
|
||||
{
|
||||
q = scan_string_run<true>(q, e);
|
||||
if (q == e)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (*q >= 0x80)
|
||||
{
|
||||
break; // ill-formed UTF-8: reported below
|
||||
}
|
||||
++q; // a quote, backslash, or control character
|
||||
}
|
||||
check_utf8_bytewise(p, n, static_cast<std::size_t>(q - p));
|
||||
}
|
||||
|
||||
inline void check_utf8(const char* s, std::size_t n)
|
||||
{
|
||||
const auto* const p = reinterpret_cast<const unsigned char*>(s); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n >= 32))
|
||||
{
|
||||
check_utf8_scan(p, n);
|
||||
return;
|
||||
}
|
||||
// short strings: the vector scan costs more than it saves; (the loop is
|
||||
// inlined with a constant start, so this path is the plain byte-wise check)
|
||||
check_utf8_bytewise(p, n, 0);
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the edits of an editable basic_json_document
|
||||
|
||||
@@ -157,7 +120,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != node_kind::object && o->kind != node_kind::null)
|
||||
{
|
||||
throw_type_error(site_of(object), exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", object.type_name());
|
||||
}
|
||||
check_utf8(key.data(), key.size());
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
@@ -208,9 +171,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(site_of(array), exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
throw_type_error(exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", array.type_name());
|
||||
}
|
||||
check_index(a, idx, a->len);
|
||||
check_index(idx, a->len);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = const_cast<node*>(nav::value(element_at<true>(m_doc, a, idx))); // NOLINT(cppcoreguidelines-pro-type-const-cast)
|
||||
assign(slot, e, a, true);
|
||||
@@ -225,7 +188,7 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array && a->kind != node_kind::null)
|
||||
{
|
||||
throw_type_error(site_of(array), exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
throw_type_error(exception_id::push_back_wrong_type, "cannot use push_back() with ", array.type_name());
|
||||
}
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
if (a->kind == node_kind::null)
|
||||
@@ -249,9 +212,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(site_of(array), exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
throw_type_error(exception_id::insert_wrong_type, "cannot use insert() with ", array.type_name());
|
||||
}
|
||||
check_index(a, idx, a->len + 1);
|
||||
check_index(idx, a->len + 1);
|
||||
const encoded e = encode(std::forward<V>(value));
|
||||
node* const slot = new_slot(e);
|
||||
node* const h = block_of(m_doc, a, 1);
|
||||
@@ -269,7 +232,7 @@ class editor
|
||||
node* const o = own(object);
|
||||
if (o->kind != node_kind::object)
|
||||
{
|
||||
throw_type_error(site_of(object), exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", object.type_name());
|
||||
}
|
||||
for (const node* k = nav::first(m_doc, o), *end = nav::end(m_doc, o); k != end; k = document_data::after(k + 1))
|
||||
{
|
||||
@@ -287,9 +250,9 @@ class editor
|
||||
node* const a = own(array);
|
||||
if (a->kind != node_kind::array)
|
||||
{
|
||||
throw_type_error(site_of(array), exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
throw_type_error(exception_id::erase_wrong_type, "cannot use erase() with ", array.type_name());
|
||||
}
|
||||
check_index(a, idx, a->len);
|
||||
check_index(idx, a->len);
|
||||
node* const h = block_of(m_doc, a, 0);
|
||||
std::memmove(h + 1 + idx, h + 2 + idx, (h->next - 2 - idx) * sizeof(node));
|
||||
--h->next;
|
||||
@@ -316,11 +279,11 @@ class editor
|
||||
return const_cast<node*>(v.m_node); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to this document
|
||||
}
|
||||
|
||||
void check_index(const node* array, std::size_t idx, std::size_t limit) const
|
||||
static void check_index(std::size_t idx, std::size_t limit)
|
||||
{
|
||||
if (idx >= limit)
|
||||
{
|
||||
throw_array_index_out_of_range(error_site(&m_doc, array), idx);
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ inline void set_moved(document_data& d, node* n, node* seq, std::size_t cap)
|
||||
e.moved_cap[n->off] = cap;
|
||||
return;
|
||||
}
|
||||
// every caller has called reserve_moved() (see above), so neither push_back() can throw
|
||||
reserve_moved(d);
|
||||
e.moved.push_back(seq);
|
||||
e.moved_cap.push_back(cap);
|
||||
n->off = static_cast<std::uint32_t>(e.moved.size() - 1);
|
||||
@@ -243,7 +243,7 @@ inline node* find_parent(const document_data& d, const node* target)
|
||||
}
|
||||
if (target == lo)
|
||||
{
|
||||
return nullptr; // LCOV_EXCL_LINE (the root, or a new value's root, which is linked)
|
||||
return nullptr;
|
||||
}
|
||||
for (;;)
|
||||
{
|
||||
|
||||
@@ -10,15 +10,11 @@
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <cstddef> // size_t
|
||||
#include <string> // string, to_string
|
||||
#include <vector> // vector
|
||||
#include <string> // string
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/detail/view/builder.hpp>
|
||||
#include <nlohmann/detail/view/document_data.hpp>
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/materialize.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -26,295 +22,8 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the value an exception is about
|
||||
|
||||
basic_json passes the value to the exception, which then names its path
|
||||
(JSON_DIAGNOSTICS) and its bytes in the source (JSON_DIAGNOSTIC_POSITIONS). A
|
||||
view's value has no parent pointer and no stored positions: both are found
|
||||
from the document and the node, when the exception is thrown (so only the
|
||||
error path pays for them). Without the two macros, a site is empty.
|
||||
*/
|
||||
class error_site
|
||||
{
|
||||
public:
|
||||
error_site() noexcept = default;
|
||||
|
||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||
error_site(const document_data* d, const node* n) noexcept
|
||||
: m_doc(d), m_node(n)
|
||||
{}
|
||||
|
||||
const document_data* doc() const noexcept
|
||||
{
|
||||
return m_doc;
|
||||
}
|
||||
const node* value() const noexcept
|
||||
{
|
||||
return m_node;
|
||||
}
|
||||
|
||||
private:
|
||||
const document_data* m_doc = nullptr;
|
||||
const node* m_node = nullptr;
|
||||
#else
|
||||
error_site(const document_data* /*d*/, const node* /*n*/) noexcept {}
|
||||
#endif
|
||||
};
|
||||
|
||||
/// the site of a view's value (the views make this a friend)
|
||||
struct view_access
|
||||
{
|
||||
template<typename View>
|
||||
static error_site site(const View& v) noexcept
|
||||
{
|
||||
return {v.m_doc, v.m_node};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename View>
|
||||
error_site site_of(const View& v) noexcept
|
||||
{
|
||||
static_assert(is_json_view<View>::value, "the site of a value is only defined for views");
|
||||
return view_access::site(v);
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
/*!
|
||||
@brief the JSON pointer tokens from the root of document @a d to node @a target
|
||||
|
||||
Nodes have no parent index, so the path is found by descending from the root:
|
||||
before the first edit, only into the subtree that contains the target (a
|
||||
container spans the next `next` nodes); after edits, elements can live in
|
||||
other sequences, so every container is searched. Iterative (the nesting depth
|
||||
is limited by memory only). False if the target is not found (then tokens is
|
||||
unspecified).
|
||||
*/
|
||||
template<bool Editable>
|
||||
bool value_path(const document_data& d, const node* target, std::vector<std::string>& tokens)
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
struct frame
|
||||
{
|
||||
const node* pos;
|
||||
const node* end;
|
||||
std::size_t index;
|
||||
bool object;
|
||||
};
|
||||
const node* const root = d.nodes;
|
||||
if (target == root)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!is_container(*root))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::vector<frame> stack;
|
||||
stack.push_back(frame{nav::first(d, root), nav::end(d, root), 0, root->kind == node_kind::object});
|
||||
// (tokens[i] leads from stack[i] to stack[i + 1], or to the target)
|
||||
for (;;)
|
||||
{
|
||||
// a copy: pop_back() and push_back() below change the stack
|
||||
const frame f = stack.back();
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
tokens.pop_back();
|
||||
continue;
|
||||
}
|
||||
const node* entry = f.pos;
|
||||
std::string token;
|
||||
if (f.object)
|
||||
{
|
||||
// (as basic_json: the key up to a null byte)
|
||||
token.assign(d.str(*entry), entry->len);
|
||||
const std::size_t nul = token.find('\0');
|
||||
if (nul != std::string::npos)
|
||||
{
|
||||
token.resize(nul);
|
||||
}
|
||||
++entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
token = std::to_string(f.index);
|
||||
}
|
||||
const node* const child = nav::value(entry);
|
||||
if (child == target)
|
||||
{
|
||||
tokens.push_back(token);
|
||||
return true;
|
||||
}
|
||||
stack.back().pos = document_data::after(entry);
|
||||
++stack.back().index;
|
||||
if (is_container(*child) && (nav::editable() || (target > child && target < child + child->next)))
|
||||
{
|
||||
tokens.push_back(token);
|
||||
stack.push_back(frame{nav::first(d, child), nav::end(d, child), 0, child->kind == node_kind::object});
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/*!
|
||||
@brief the bytes [start, end) of node @a n in the source, as materialize() sets them
|
||||
|
||||
Scalars as position_source::set_scalar; an array or object from its opening
|
||||
bracket to its closing bracket, which is found by following the chain of last
|
||||
elements to a scalar (or an empty container) and scanning from there to the
|
||||
closing brackets. None (npos) for values written by edits, and for containers
|
||||
whose last element has none, as with materialize().
|
||||
*/
|
||||
template<bool Editable>
|
||||
void value_range(const document_data& d, const node* n, std::size_t& start, std::size_t& end)
|
||||
{
|
||||
using nav = navigation<Editable>;
|
||||
position_source p;
|
||||
start = std::string::npos;
|
||||
end = std::string::npos;
|
||||
if (!is_container(*n))
|
||||
{
|
||||
p.set_scalar(d, *n);
|
||||
start = p.start;
|
||||
end = p.end;
|
||||
return;
|
||||
}
|
||||
// the open brackets: n, then the last element as long as it is a non-empty container
|
||||
std::size_t closing = 0;
|
||||
std::size_t pos = std::string::npos;
|
||||
for (const node* c = n;;)
|
||||
{
|
||||
if (!is_container(*c))
|
||||
{
|
||||
p.set_scalar(d, *c);
|
||||
pos = p.end;
|
||||
break;
|
||||
}
|
||||
if ((c->flags & node_flags::is_new) != 0 || c->off >= d.size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
++closing;
|
||||
const node* const first = nav::first(d, c);
|
||||
const node* const last_end = nav::end(d, c);
|
||||
if (first == last_end)
|
||||
{
|
||||
pos = std::size_t{c->off} + 1;
|
||||
break;
|
||||
}
|
||||
const std::size_t value_offset = c->kind == node_kind::object ? 1 : 0;
|
||||
const node* last = first;
|
||||
for (const node* e = document_data::after(first + value_offset); e != last_end; e = document_data::after(e + value_offset))
|
||||
{
|
||||
last = e;
|
||||
}
|
||||
c = nav::value(last + value_offset);
|
||||
}
|
||||
for (; closing != 0 && pos != std::string::npos; --closing)
|
||||
{
|
||||
pos = position_source::container_end(d.base[0], d.size, pos);
|
||||
}
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
start = n->off;
|
||||
end = pos;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the context basic_json puts in front of the message of an exception about
|
||||
/// a value: "(/path) " with JSON_DIAGNOSTICS (none for the root), then
|
||||
/// "(bytes a-b) " with JSON_DIAGNOSTIC_POSITIONS (also for the root); with
|
||||
/// JSON_DIAGNOSTICS, nothing for a value that is no longer part of the document
|
||||
///
|
||||
/// This and the throw helpers that take an error_site are templates, so that
|
||||
/// the path and range code is instantiated only where an exception can be
|
||||
/// thrown: instantiated in every translation unit (as a non-template inline
|
||||
/// function makes it), it changed GCC's early inlining of unrelated view
|
||||
/// functions, so that GCC 13 no longer inlined a recursive walk over a view
|
||||
/// into itself (2.7 times the instructions).
|
||||
#if JSON_DIAGNOSTICS || JSON_DIAGNOSTIC_POSITIONS
|
||||
template<typename Unused = void>
|
||||
std::string diagnostics(const error_site site)
|
||||
{
|
||||
if (site.doc() == nullptr || site.value() == nullptr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
const document_data& d = *site.doc();
|
||||
const bool edited = d.edits != nullptr;
|
||||
std::string context;
|
||||
#if JSON_DIAGNOSTICS
|
||||
std::vector<std::string> tokens;
|
||||
// a value that is no longer part of the document has no path, and its
|
||||
// node still holds the bytes it had in the source: no context at all
|
||||
const bool found = edited ? value_path<true>(d, site.value(), tokens) : value_path<false>(d, site.value(), tokens);
|
||||
if (!found)
|
||||
{
|
||||
// context is still empty; returning it keeps the copy elided (-Wnrvo)
|
||||
return context;
|
||||
}
|
||||
if (!tokens.empty())
|
||||
{
|
||||
// the root value has no path, but still has a byte range
|
||||
std::string path;
|
||||
for (const std::string& token : tokens)
|
||||
{
|
||||
path.push_back('/');
|
||||
path += detail::escape(token);
|
||||
}
|
||||
context = concat('(', path, ") ");
|
||||
}
|
||||
#endif
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::size_t start = 0;
|
||||
std::size_t end = 0;
|
||||
if (edited)
|
||||
{
|
||||
value_range<true>(d, site.value(), start, end);
|
||||
}
|
||||
else
|
||||
{
|
||||
value_range<false>(d, site.value(), start, end);
|
||||
}
|
||||
if (start != std::string::npos && end != std::string::npos)
|
||||
{
|
||||
context += concat("(bytes ", std::to_string(start), "-", std::to_string(end), ") ");
|
||||
}
|
||||
#endif
|
||||
return context;
|
||||
}
|
||||
#else
|
||||
template<typename Unused = void>
|
||||
std::string diagnostics(const error_site /*site*/)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the context of an exception about view @a v (see exception_prefix())
|
||||
template<typename View>
|
||||
std::string diagnostics_of(const View& v)
|
||||
{
|
||||
return diagnostics<>(site_of(v));
|
||||
}
|
||||
|
||||
// Exceptions are thrown out of line, so that the accessors that may throw stay
|
||||
// small enough to be inlined. The overloads with an error_site put its context
|
||||
// in front of the message.
|
||||
//
|
||||
// The overloads without one are for exceptions that are not about a value of a
|
||||
// document: limits and invalid input of the editing and image functions, a
|
||||
// discarded view (it has no node), a view of another document (its path would
|
||||
// be one in that document), and those that basic_json creates without a
|
||||
// context too (the errors of json_pointer, invalid UTF-8). The one exception
|
||||
// about a value that has no site is key(), see view_iterator::key().
|
||||
// small enough to be inlined.
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_type_error(exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
@@ -336,24 +45,6 @@ std::string diagnostics_of(const View& v)
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, msg, nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_type_error(const error_site site, exception_id id, const char* prefix, const char* type)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(type_error::create(id, concat(diagnostics<>(site), prefix, type), nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_out_of_range(const error_site site, exception_id id, const std::string& msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(out_of_range::create(id, concat(diagnostics<>(site), msg), nullptr));
|
||||
}
|
||||
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_invalid_iterator(const error_site site, exception_id id, const char* msg)
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(invalid_iterator::create(id, concat(diagnostics<>(site), msg), nullptr));
|
||||
}
|
||||
|
||||
/// a parse error without a position (as those of json_pointer)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_parse_error(exception_id id, const std::string& msg)
|
||||
{
|
||||
@@ -362,23 +53,22 @@ template<typename Unused = void>
|
||||
|
||||
/// out_of_range.401: the array index @a idx is not below the size
|
||||
template<typename Index>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const error_site site, const Index idx)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_out_of_range(const Index idx)
|
||||
{
|
||||
throw_out_of_range(site, exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
throw_out_of_range(exception_id::array_index_out_of_range, concat("array index ", std::to_string(idx), " is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.402: the JSON Pointer token '-' of an array of @a size elements
|
||||
template<typename Unused = void>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_past_end(const error_site site, std::size_t size)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE inline void throw_array_index_past_end(std::size_t size)
|
||||
{
|
||||
throw_out_of_range(site, exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
throw_out_of_range(exception_id::pointer_past_the_end_index, concat("array index '-' (", std::to_string(size), ") is out of range"));
|
||||
}
|
||||
|
||||
/// out_of_range.403: the object has no member @a key
|
||||
template<typename Key>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const error_site site, const Key& key)
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_key_not_found(const Key& key)
|
||||
{
|
||||
throw_out_of_range(site, exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
throw_out_of_range(exception_id::key_not_found, concat("key '", key, "' not found"));
|
||||
}
|
||||
|
||||
/// SAX handler of the error replay: accepts every event without storing it,
|
||||
@@ -422,12 +112,10 @@ struct throwing_sax
|
||||
return true;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START (throw_parse_failure parses JSON only)
|
||||
bool binary(binary_t& /*unused*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
bool start_object(std::size_t /*unused*/)
|
||||
{
|
||||
|
||||
@@ -80,8 +80,7 @@ constexpr std::uint64_t image_limit = 0xFFFFFFF0u;
|
||||
/// Copy the current structure of an edited document into nodes in document
|
||||
/// order, as the parser would have written them. Text written by edits is
|
||||
/// appended to text_tail (number tokens) and arena_tail (strings); floats that
|
||||
/// are not finite become null, as dump() writes them. Nodes keep the flag
|
||||
/// is_new: they have no source position (JSON_DIAGNOSTIC_POSITIONS).
|
||||
/// are not finite become null, as dump() writes them.
|
||||
inline void compact_nodes(const document_data& d, std::size_t arena_size, std::vector<node>& out, std::string& text_tail, std::string& arena_tail)
|
||||
{
|
||||
struct frame
|
||||
@@ -104,18 +103,16 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
arena_tail.append(d.str(s), s.len);
|
||||
r.flags = node_flags::escaped;
|
||||
}
|
||||
r.flags = static_cast<std::uint8_t>(r.flags | (s.flags & node_flags::is_new));
|
||||
return r;
|
||||
};
|
||||
const auto copy_node = [&](const node * v)
|
||||
{
|
||||
node r = *v;
|
||||
const auto is_new = static_cast<std::uint8_t>(v->flags & node_flags::is_new);
|
||||
switch (static_cast<value_t>(v->kind))
|
||||
{
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
r.flags = is_new;
|
||||
r.flags = 0;
|
||||
r.extra = 0;
|
||||
r.off = (v->flags & (node_flags::moved | node_flags::is_new)) != 0 ? 0 : v->off;
|
||||
r.len = 0; // counted below
|
||||
@@ -132,7 +129,7 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(d.str(*v), number_length(*v));
|
||||
}
|
||||
r.flags = is_new;
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::number_float:
|
||||
if ((v->flags & node_flags::storage) == node_flags::edited)
|
||||
@@ -142,23 +139,22 @@ inline void compact_nodes(const document_data& d, std::size_t arena_size, std::v
|
||||
{
|
||||
r = node{}; // nan and infinity: null, as dump() writes them
|
||||
r.kind = node_kind::null;
|
||||
r.flags = is_new;
|
||||
break;
|
||||
}
|
||||
r.off = static_cast<std::uint32_t>(d.size + text_tail.size());
|
||||
text_tail.append(t, v->len);
|
||||
r.extra = 0xFFFFu; // the digit layout is not recorded
|
||||
}
|
||||
r.flags = is_new;
|
||||
r.flags = 0;
|
||||
break;
|
||||
case value_t::boolean:
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & (node_flags::is_true | node_flags::is_new));
|
||||
r.flags = static_cast<std::uint8_t>(v->flags & node_flags::is_true);
|
||||
break;
|
||||
case value_t::null:
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
r.flags = is_new;
|
||||
r.flags = 0;
|
||||
break;
|
||||
}
|
||||
out.push_back(r);
|
||||
@@ -258,81 +254,6 @@ struct number_token
|
||||
bool is_float; ///< a fraction or an exponent
|
||||
};
|
||||
|
||||
/// advance over ASCII digits, eight bytes at a time
|
||||
inline const unsigned char* digit_run(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
while (e - p >= 8)
|
||||
{
|
||||
// (the digits become 0..9; a byte above 9, or with bit 7 set, is no digit. A carry
|
||||
// out of a byte with bit 7 set only affects the bytes above, after the first one
|
||||
// that count_trailing_zeros finds.)
|
||||
const std::uint64_t x = read_eight_bytes(p) ^ (ones * 0x30u);
|
||||
const std::uint64_t not_digit = ((x + (ones * 0x76u)) | x) & (ones * 0x80u);
|
||||
if (not_digit != 0)
|
||||
{
|
||||
return p + (static_cast<std::size_t>(count_trailing_zeros(not_digit)) / 8);
|
||||
}
|
||||
p += 8;
|
||||
}
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// whether the n bytes at p are ASCII digits (n > 0)
|
||||
inline bool all_digits(const unsigned char* p, std::size_t n) noexcept
|
||||
{
|
||||
const auto digits = [](const unsigned char* q)
|
||||
{
|
||||
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||
const std::uint64_t x = read_eight_bytes(q) ^ (ones * 0x30u);
|
||||
return (((x + (ones * 0x76u)) | x) & (ones * 0x80u)) == 0;
|
||||
};
|
||||
if (n >= 8)
|
||||
{
|
||||
for (std::size_t i = 0; i + 8 < n; i += 8)
|
||||
{
|
||||
if (!digits(p + i))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return digits(p + n - 8); // (overlapping the bytes before: they are digits)
|
||||
}
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (!is_digit(p[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Whether a float token has the shape that the parser records for most of
|
||||
/// them: [-]digits.digits, with the numbers of digits of the node's layout
|
||||
/// (extra). Then the token is a number of that layout, and the general scan
|
||||
/// that follows is not needed.
|
||||
inline bool plain_float_token(const unsigned char* s, std::size_t len, std::uint16_t extra) noexcept
|
||||
{
|
||||
const std::size_t int_digits = extra & 0xFFu;
|
||||
const std::size_t frac_digits = extra >> 8u;
|
||||
if (int_digits == 0 || int_digits >= 255 || frac_digits == 0 || frac_digits >= 255 || len == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const std::size_t negative = *s == '-' ? 1 : 0;
|
||||
if (len != negative + int_digits + 1 + frac_digits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const unsigned char* const p = s + negative;
|
||||
return !(int_digits > 1 && *p == '0') && p[int_digits] == '.' && all_digits(p, int_digits) && all_digits(p + int_digits + 1, frac_digits);
|
||||
}
|
||||
|
||||
/// whether [s, s + len) is a JSON number (the grammar the parser accepts)
|
||||
inline bool scan_number_token(const unsigned char* s, std::size_t len, number_token& t)
|
||||
{
|
||||
@@ -351,7 +272,11 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
}
|
||||
else if (*p >= '1' && *p <= '9')
|
||||
{
|
||||
p = digit_run(p + 1, e);
|
||||
do
|
||||
{
|
||||
++p;
|
||||
}
|
||||
while (p != e && is_digit(*p));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -363,7 +288,10 @@ inline bool scan_number_token(const unsigned char* s, std::size_t len, number_to
|
||||
if (p != e && *p == '.')
|
||||
{
|
||||
const unsigned char* const f0 = ++p;
|
||||
p = digit_run(p, e);
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
if (p == f0)
|
||||
{
|
||||
return false;
|
||||
@@ -474,15 +402,10 @@ struct float_range
|
||||
template<typename Check>
|
||||
bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
{
|
||||
// (save() writes the ranges in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const byte_range & a, const byte_range & b)
|
||||
std::sort(ranges.begin(), ranges.end(), [](const byte_range & a, const byte_range & b)
|
||||
{
|
||||
return a.off != b.off ? a.off < b.off : a.len < b.len;
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
});
|
||||
std::size_t end = 0;
|
||||
for (std::size_t i = 0; i < ranges.size(); ++i)
|
||||
{
|
||||
@@ -504,8 +427,7 @@ bool check_distinct_ranges(std::vector<byte_range>& ranges, Check check)
|
||||
/// record layouts that match it), tokens must not overlap.
|
||||
inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned char* text)
|
||||
{
|
||||
// (save() writes the tokens in the order of the nodes, which is sorted already)
|
||||
const auto before = [](const float_range & a, const float_range & b)
|
||||
std::sort(ranges.begin(), ranges.end(), [](const float_range & a, const float_range & b)
|
||||
{
|
||||
if (a.off != b.off)
|
||||
{
|
||||
@@ -516,11 +438,7 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
return a.len < b.len;
|
||||
}
|
||||
return a.extra < b.extra;
|
||||
};
|
||||
if (!std::is_sorted(ranges.begin(), ranges.end(), before))
|
||||
{
|
||||
std::sort(ranges.begin(), ranges.end(), before);
|
||||
}
|
||||
});
|
||||
std::size_t end = 0;
|
||||
std::size_t i = 0;
|
||||
while (i < ranges.size())
|
||||
@@ -530,18 +448,14 @@ inline bool check_float_ranges(std::vector<float_range>& ranges, const unsigned
|
||||
{
|
||||
return false;
|
||||
}
|
||||
number_token t{};
|
||||
const unsigned char* const s = text + r.off;
|
||||
std::uint16_t layout = r.extra;
|
||||
if (!plain_float_token(s, r.len, r.extra))
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
{
|
||||
number_token t{};
|
||||
if (!scan_number_token(s, r.len, t) || !float_token_finite(s, r.len, t))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
layout = float_layout(t);
|
||||
return false;
|
||||
}
|
||||
// the digit layout the parser records (or "many", as compaction writes it)
|
||||
const std::uint16_t layout = float_layout(t);
|
||||
for (; i < ranges.size() && ranges[i].off == r.off && ranges[i].len == r.len; ++i)
|
||||
{
|
||||
if (ranges[i].extra != layout && ranges[i].extra != 0xFFFFu)
|
||||
@@ -601,15 +515,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
std::vector<float_range> floats;
|
||||
const auto check_string = [&](const node & n) -> bool
|
||||
{
|
||||
if ((n.flags & ~(node_flags::escaped | node_flags::is_new)) != 0 || n.extra != 0)
|
||||
if ((n.flags & ~node_flags::escaped) != 0 || n.extra != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const bool decoded = (n.flags & node_flags::escaped) != 0;
|
||||
const std::size_t limit = decoded ? arena_size : text_size;
|
||||
// (next: the source offset of a decoded string's opening quote plus
|
||||
// one, or 0; see node)
|
||||
if (n.off > limit || n.len > limit - n.off || n.next > text_size)
|
||||
if (n.off > limit || n.len > limit - n.off)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -681,13 +593,13 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
{
|
||||
case value_t::null:
|
||||
// (the offset of a literal is read to size the output of dump())
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.off > text_size)
|
||||
if (n.flags != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case value_t::boolean:
|
||||
if ((n.flags & ~(node_flags::is_true | node_flags::is_new)) != 0 || n.extra != 0 || n.off > text_size)
|
||||
if ((n.flags & ~node_flags::is_true) != 0 || n.extra != 0 || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -701,7 +613,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::number_integer:
|
||||
case value_t::number_unsigned:
|
||||
case value_t::number_float:
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || !number_in_bounds(n))
|
||||
if (n.flags != 0 || !number_in_bounds(n))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -723,7 +635,7 @@ inline bool check_image(const node* nodes, std::size_t count, const unsigned cha
|
||||
case value_t::object:
|
||||
{
|
||||
const std::size_t limit = stack.empty() ? count : stack.back().end;
|
||||
if ((n.flags & ~node_flags::is_new) != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
if (n.flags != 0 || n.extra != 0 || n.next == 0 || n.next > limit - i || n.off > text_size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -787,6 +699,10 @@ inline void load_image(document_data& d, const std::uint8_t* image, std::size_t
|
||||
d.edits.reset();
|
||||
d.base[2] = nullptr;
|
||||
d.owned.clear();
|
||||
if (d.owned_image.empty() || image != d.owned_image.data())
|
||||
{
|
||||
d.owned_image.clear();
|
||||
}
|
||||
d.arena.clear();
|
||||
d.indexes.clear();
|
||||
d.index_slots.clear();
|
||||
|
||||
@@ -105,10 +105,7 @@ class view_iterator
|
||||
}
|
||||
|
||||
/// the key of the current object member; throws invalid_iterator.207 for
|
||||
/// other iterators, like basic_json's iterators (but without the context
|
||||
/// JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS add: the iterator does not
|
||||
/// know the value it iterates over, and keeping it would make iterators
|
||||
/// larger, which keeps GCC from inlining recursive walks)
|
||||
/// other iterators, like basic_json's iterators
|
||||
string_view_t key() const
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_value_offset == 0))
|
||||
|
||||
@@ -40,15 +40,6 @@
|
||||
#define NLOHMANN_VIEW_NOINLINE
|
||||
#endif
|
||||
|
||||
// GCC: a function compiled as written, without the clones in which interprocedural
|
||||
// constant propagation drops an argument that every caller passes as the same constant
|
||||
// (calling such a clone changes the code GCC generates for the parse loop that calls it)
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#define NLOHMANN_VIEW_NOCLONE __attribute__((noclone))
|
||||
#else
|
||||
#define NLOHMANN_VIEW_NOCLONE
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define NLOHMANN_VIEW_NODISCARD __attribute__((warn_unused_result))
|
||||
#elif defined(_MSC_VER)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#undef NLOHMANN_VIEW_UNLIKELY
|
||||
#undef NLOHMANN_VIEW_ALWAYS_INLINE
|
||||
#undef NLOHMANN_VIEW_NOINLINE
|
||||
#undef NLOHMANN_VIEW_NOCLONE
|
||||
#undef NLOHMANN_VIEW_NODISCARD
|
||||
#undef NLOHMANN_VIEW_THROW
|
||||
#undef NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include <nlohmann/detail/view/macro_scope.hpp>
|
||||
#include <nlohmann/detail/view/node.hpp>
|
||||
#include <nlohmann/detail/view/number.hpp>
|
||||
#include <nlohmann/detail/view/scan.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -27,184 +26,6 @@ namespace detail
|
||||
namespace view
|
||||
{
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/*!
|
||||
@brief the source positions of the values materialize() replays
|
||||
|
||||
Stands in for the lexer of json_sax_dom_parser, which reads the positions of
|
||||
the values it stores from these three functions (diagnostic_positions). npos
|
||||
is a value without a source position. One position_source serves a whole
|
||||
materialize() call: before each value is handed to the handler, the range of
|
||||
that value is set (or cleared), overwriting that of the value before.
|
||||
*/
|
||||
struct position_source
|
||||
{
|
||||
/// the token, of which diagnostic_positions only reads the length
|
||||
struct token
|
||||
{
|
||||
std::size_t length;
|
||||
std::size_t size() const noexcept
|
||||
{
|
||||
return length;
|
||||
}
|
||||
};
|
||||
|
||||
std::size_t start = std::string::npos; ///< the first byte of the next value
|
||||
std::size_t end = std::string::npos; ///< one past the next value (start_object/start_array: one past the bracket)
|
||||
|
||||
std::size_t get_chars_read_total() const noexcept
|
||||
{
|
||||
return end;
|
||||
}
|
||||
std::size_t get_token_start_position() const noexcept
|
||||
{
|
||||
return start;
|
||||
}
|
||||
token get_string() const noexcept
|
||||
{
|
||||
return token{end - start};
|
||||
}
|
||||
|
||||
/// the range [first, last)
|
||||
void set(std::size_t first, std::size_t last) noexcept
|
||||
{
|
||||
start = first;
|
||||
end = last;
|
||||
}
|
||||
|
||||
/// no range: the next value has no source position
|
||||
void clear() noexcept
|
||||
{
|
||||
start = std::string::npos;
|
||||
end = std::string::npos;
|
||||
}
|
||||
|
||||
/// one past the closing quote of the string whose opening quote is at
|
||||
/// src[quote], or npos (the scan is bounded by size, as images can be
|
||||
/// loaded without a check)
|
||||
static std::size_t string_end(const char* src, std::size_t size, std::size_t quote) noexcept
|
||||
{
|
||||
for (std::size_t i = quote + 1; i < size; ++i)
|
||||
{
|
||||
if (src[i] == '\\')
|
||||
{
|
||||
++i;
|
||||
}
|
||||
else if (src[i] == '"')
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
/// one past the closing bracket of the array or object whose last element
|
||||
/// ends at src[pos] (or whose opening bracket is at src[pos - 1]), or npos;
|
||||
/// in between, there can only be whitespace, comments, and a trailing comma
|
||||
/// (the source was validated when it was parsed)
|
||||
static std::size_t container_end(const char* src, std::size_t size, std::size_t pos) noexcept
|
||||
{
|
||||
const auto* const b = reinterpret_cast<const unsigned char*>(src); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast): the text as bytes, as the builder reads it
|
||||
const unsigned char* const e = b + size;
|
||||
while (pos < size)
|
||||
{
|
||||
switch (src[pos])
|
||||
{
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case ',':
|
||||
++pos;
|
||||
break;
|
||||
case ']':
|
||||
case '}':
|
||||
return pos + 1;
|
||||
case '/':
|
||||
if (pos + 1 < size && src[pos + 1] == '/')
|
||||
{
|
||||
pos = static_cast<std::size_t>(skip_line_comment(b + pos + 2, e) - b);
|
||||
break;
|
||||
}
|
||||
if (pos + 1 < size && src[pos + 1] == '*')
|
||||
{
|
||||
// (unterminated: pos is size, which ends the loop)
|
||||
pos = static_cast<std::size_t>(find_block_comment_end(b + pos + 2, e) - b) + 2;
|
||||
break;
|
||||
}
|
||||
return std::string::npos;
|
||||
default:
|
||||
return std::string::npos;
|
||||
}
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
/// the range of the scalar n of document d
|
||||
void set_scalar(const document_data& d, const node& n) noexcept
|
||||
{
|
||||
if ((n.flags & node_flags::is_new) != 0)
|
||||
{
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
std::size_t first = std::string::npos;
|
||||
std::size_t last = std::string::npos;
|
||||
switch (n.kind)
|
||||
{
|
||||
case node_kind::string:
|
||||
if ((n.flags & node_flags::storage) == 0)
|
||||
{
|
||||
// off is the first byte after the opening quote
|
||||
if (n.off != 0)
|
||||
{
|
||||
first = n.off - 1;
|
||||
last = n.off + std::size_t{n.len} + 1;
|
||||
}
|
||||
}
|
||||
else if ((n.flags & node_flags::storage) == node_flags::escaped && n.next != 0)
|
||||
{
|
||||
// next is the offset of the opening quote plus one
|
||||
first = n.next - 1;
|
||||
last = string_end(d.base[0], d.size, first);
|
||||
}
|
||||
break;
|
||||
case node_kind::number_integer:
|
||||
case node_kind::number_unsigned:
|
||||
case node_kind::number_float:
|
||||
if ((n.flags & node_flags::storage) == 0)
|
||||
{
|
||||
first = n.off;
|
||||
last = n.off + number_length(n);
|
||||
}
|
||||
break;
|
||||
case node_kind::boolean:
|
||||
first = n.off;
|
||||
last = n.off + ((n.flags & node_flags::is_true) != 0 ? 4u : 5u);
|
||||
break;
|
||||
case node_kind::null:
|
||||
first = n.off;
|
||||
last = n.off + 4u;
|
||||
break;
|
||||
case node_kind::object: // LCOV_EXCL_LINE
|
||||
case node_kind::array:
|
||||
case node_kind::link:
|
||||
default:
|
||||
break; // LCOV_EXCL_LINE
|
||||
}
|
||||
// (a crafted image can point past the text)
|
||||
if (last == std::string::npos || last > d.size)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
set(first, last);
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
/*!
|
||||
@brief the basic_json value of the subtree at n
|
||||
|
||||
@@ -212,24 +33,14 @@ The subtree is replayed into the SAX handler that parse() uses to build its
|
||||
values, so the result is the value parse() would produce: duplicate keys keep
|
||||
the last value, and with JSON_DIAGNOSTICS the parent pointers are set. It is
|
||||
iterative, so the nesting depth is limited by memory only, as for parse().
|
||||
|
||||
With JSON_DIAGNOSTIC_POSITIONS, the values get their source positions as with
|
||||
parse(): position_source stands in for the lexer. Values written by edits have
|
||||
none (npos), and neither has an array or object whose elements changed, or
|
||||
whose last element has none (the end of its closing bracket is found by
|
||||
scanning from the end of the last element).
|
||||
Without a lexer the handler records no source positions
|
||||
(JSON_DIAGNOSTIC_POSITIONS).
|
||||
*/
|
||||
template<typename BasicJsonType, bool Editable>
|
||||
BasicJsonType materialize(const document_data& d, const node* n)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using input_t = iterator_input_adapter<const char*>;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
using lexer_t = position_source;
|
||||
#else
|
||||
using lexer_t = lexer<BasicJsonType, input_t>; // (the default: unused, as no lexer is passed)
|
||||
#endif
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, input_t, true, lexer_t>;
|
||||
using sax_t = json_sax_dom_parser<BasicJsonType, iterator_input_adapter<const char*>, true>;
|
||||
using nav = navigation<Editable>;
|
||||
|
||||
// the open containers: their next element (or key of the next member),
|
||||
@@ -241,15 +52,9 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
const node* pos;
|
||||
const node* end;
|
||||
bool object;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
std::size_t start; ///< the opening bracket (npos: unknown)
|
||||
std::size_t last_end; ///< the end of the last element read (npos: unknown)
|
||||
#endif
|
||||
};
|
||||
|
||||
#if !JSON_DIAGNOSTIC_POSITIONS
|
||||
// a scalar needs neither the handler nor the stack of open containers
|
||||
// (with positions, it goes through the handler, which sets them)
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
{
|
||||
case value_t::string:
|
||||
@@ -271,16 +76,10 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
BasicJsonType result;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
position_source positions;
|
||||
sax_t sax(result, true, &positions);
|
||||
#else
|
||||
sax_t sax(result, true);
|
||||
#endif
|
||||
const string_t no_token {};
|
||||
const string_t no_token{};
|
||||
// (shallow subtrees on the stack: a call for a small value then allocates only for the value)
|
||||
std::array<frame, 16> shallow; // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init): filled as containers open
|
||||
std::vector<frame> deep{};
|
||||
@@ -295,16 +94,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
std::size_t depth = 0;
|
||||
for (;;)
|
||||
{
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
if (!is_container(*n))
|
||||
{
|
||||
positions.set_scalar(d, *n);
|
||||
if (depth != 0)
|
||||
{
|
||||
open[depth - 1].last_end = positions.end;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// false positive: n comes from nav::value(), which never returns null for a valid index
|
||||
// @infer-ignore NULLPTR_DEREFERENCE
|
||||
switch (static_cast<value_t>(n->kind))
|
||||
@@ -313,17 +102,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
case value_t::array:
|
||||
{
|
||||
const bool object = n->kind == node_kind::object;
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// (moved containers are also new)
|
||||
if ((n->flags & node_flags::is_new) != 0 || n->off >= d.size)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
positions.set(std::size_t{n->off}, std::size_t{n->off} + 1);
|
||||
}
|
||||
#endif
|
||||
if (object)
|
||||
{
|
||||
sax.start_object(n->len);
|
||||
@@ -340,13 +118,7 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
open = deep.data();
|
||||
capacity = deep.size();
|
||||
}
|
||||
// (with positions: until an element is read, the container's
|
||||
// own bracket is the last thing read)
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
, positions.start, positions.end
|
||||
#endif
|
||||
};
|
||||
open[depth++] = frame{nav::first(d, n), nav::end(d, n), object};
|
||||
break;
|
||||
}
|
||||
case value_t::string:
|
||||
@@ -387,31 +159,6 @@ BasicJsonType materialize(const document_data& d, const node* n)
|
||||
frame& f = open[depth - 1];
|
||||
if (f.pos == f.end)
|
||||
{
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
if (f.start == std::string::npos || f.last_end == std::string::npos)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::size_t end = position_source::container_end(d.base[0], d.size, f.last_end);
|
||||
if (end == std::string::npos)
|
||||
{
|
||||
positions.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// the closing bracket, as the lexer reports it (end_object()
|
||||
// and end_array() only read its end; the start of the
|
||||
// container was set when it was opened)
|
||||
positions.set(end - 1, end);
|
||||
}
|
||||
}
|
||||
if (depth > 1)
|
||||
{
|
||||
open[depth - 2].last_end = positions.end;
|
||||
}
|
||||
#endif
|
||||
if (f.object)
|
||||
{
|
||||
sax.end_object();
|
||||
|
||||
@@ -69,7 +69,7 @@ struct node
|
||||
std::uint16_t extra; ///< numbers: integer digits (low byte) and fraction digits (high byte), 255 = "many"; objects: number of the object's key hash index (1-based, 0 = none); otherwise 0
|
||||
std::uint32_t off; ///< source offset (string content, number token, literal, bracket); arena offset if escaped/edited; number of the element sequence if moved
|
||||
std::uint32_t len; ///< string: decoded bytes; float: token bytes; array/object: element count
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence); escaped string: source offset of the opening quote plus one (JSON_DIAGNOSTIC_POSITIONS; 0: unknown)
|
||||
std::uint32_t next; ///< array/object: number of nodes of the subtree (its extent in the enclosing sequence)
|
||||
};
|
||||
static_assert(sizeof(node) == 16, "node must stay 16 bytes");
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ index_status array_index(const StringType& s, std::size_t& idx) noexcept
|
||||
}
|
||||
|
||||
/// throws the exception json_pointer::array_index throws for this status
|
||||
/// (without a context, as json_pointer::array_index)
|
||||
template<typename StringType>
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE void throw_array_index_error(index_status status, const StringType& s)
|
||||
{
|
||||
@@ -84,7 +83,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_key_not_found(site_of(cur), token);
|
||||
throw_key_not_found(token);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -96,7 +95,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_array_index_past_end(site_of(cur), cur.size());
|
||||
throw_array_index_past_end(cur.size());
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -115,7 +114,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (mode == pointer_mode::checked)
|
||||
{
|
||||
throw_array_index_out_of_range(site_of(cur), idx);
|
||||
throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return View();
|
||||
}
|
||||
@@ -125,7 +124,7 @@ View resolve_pointer(View cur, const Tokens& tokens, pointer_mode mode)
|
||||
{
|
||||
if (throwing)
|
||||
{
|
||||
throw_out_of_range(site_of(cur), exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||
throw_out_of_range(exception_id::pointer_unresolved, concat("unresolved reference token '", token, "'"));
|
||||
}
|
||||
return View();
|
||||
}
|
||||
|
||||
@@ -172,83 +172,6 @@ NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits(const unsigned char
|
||||
return p;
|
||||
}
|
||||
|
||||
/// advance over the text of a line comment (after its "//") to the line
|
||||
/// break or the end (with NulIsEnd also a null byte, which is left for the
|
||||
/// caller to see, as in parse())
|
||||
template<bool NulIsEnd = false>
|
||||
inline const unsigned char* skip_line_comment(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (p != e && *p != '\n' && *p != '\r' && !(NulIsEnd && *p == 0))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// the "*/" that ends a block comment whose text starts at p (after its "/*");
|
||||
/// if it is unterminated, the end (with NulIsEnd also a null byte), which is
|
||||
/// where the caller reports the error
|
||||
template<bool NulIsEnd = false>
|
||||
inline const unsigned char* find_block_comment_end(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (p != e && !(NulIsEnd && *p == 0) && !(*p == '*' && p + 1 != e && p[1] == '/'))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/// The digits of a fraction, whose number varies from one number to the next: skip_digits() with 16 bytes compared at
|
||||
/// once and the end of the run taken from a bit mask (SSE2), instead of a compare and a branch for each digit. That
|
||||
/// is about a third of the instructions for a fraction of 12 digits, and no branch that is mispredicted when the
|
||||
/// lengths vary. Where there is no SSE2, skip_digits() is used.
|
||||
#if NLOHMANN_VIEW_SSE2
|
||||
// The last bytes of the input, one at a time. GCC unrolls this loop in every inlined copy of the function below, which
|
||||
// makes the parse loop 4 KiB larger and slows down documents that have hardly any fractions; out of line, it does
|
||||
// not. Clang does better with the loop inline (a call in the parse loop costs it more than the unrolling).
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
inline NLOHMANN_VIEW_NOINLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
#else
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_digits_tail(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (p != e && is_digit(*p))
|
||||
{
|
||||
++p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
#endif
|
||||
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
while (e - p >= 16)
|
||||
{
|
||||
const __m128i in = _mm_loadu_si128(static_cast<const __m128i*>(static_cast<const void*>(p))); // NOLINT(bugprone-casting-through-void)
|
||||
// a digit is above '/' and below ':' (signed compares: a byte with the top bit set is negative, and neither)
|
||||
const __m128i digits = _mm_and_si128(_mm_cmpgt_epi8(in, _mm_set1_epi8('/')), _mm_cmplt_epi8(in, _mm_set1_epi8(':')));
|
||||
const auto not_digit = static_cast<std::uint64_t>(~static_cast<unsigned>(_mm_movemask_epi8(digits)) & 0xFFFFu);
|
||||
if (not_digit != 0)
|
||||
{
|
||||
return p + count_trailing_zeros(not_digit);
|
||||
}
|
||||
p += 16;
|
||||
}
|
||||
return skip_digits_tail(p, e);
|
||||
}
|
||||
#else
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE const unsigned char* skip_fraction_digits(const unsigned char* p, const unsigned char* e) noexcept
|
||||
{
|
||||
return skip_digits(p, e);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// powers of ten up to 10^19 as integers
|
||||
inline std::uint64_t int_pow10(unsigned k) noexcept
|
||||
{
|
||||
|
||||
@@ -47,7 +47,6 @@ class output_buffer
|
||||
: m_out(sized(out, estimate))
|
||||
, m_pos(&m_out[0])
|
||||
, m_end(m_pos + m_out.size())
|
||||
, m_reserved((std::max)(estimate, m_out.size()))
|
||||
{}
|
||||
|
||||
void finish()
|
||||
@@ -117,12 +116,11 @@ class output_buffer
|
||||
const auto used = static_cast<std::size_t>(m_pos - m_out.data());
|
||||
// (a step does not go beyond the reserved estimate, so that a good
|
||||
// estimate is never copied to a larger allocation; not a clamp: the
|
||||
// lower bound used + n + 256 can exceed m_reserved, and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_reserved), used + n + 256);
|
||||
if (size > m_reserved)
|
||||
// lower bound used + n + 256 can exceed capacity(), and then it must win)
|
||||
const std::size_t size = (std::max)((std::min)(m_out.size() + step, m_out.capacity()), used + n + 256);
|
||||
if (size > m_out.capacity())
|
||||
{
|
||||
m_reserved = (std::max)(m_reserved * 2, size);
|
||||
m_out.reserve(m_reserved);
|
||||
m_out.reserve((std::max)(m_out.capacity() * 2, size));
|
||||
}
|
||||
m_out.resize(size);
|
||||
m_pos = &m_out[0] + used;
|
||||
@@ -132,9 +130,6 @@ class output_buffer
|
||||
StringType& m_out;
|
||||
char* m_pos;
|
||||
char* m_end;
|
||||
/// what was last passed to reserve(), a lower bound of the capacity
|
||||
/// (StringType need not have a capacity() member)
|
||||
std::size_t m_reserved;
|
||||
};
|
||||
|
||||
/// The length of the run at s that dump() writes unchanged without
|
||||
@@ -452,27 +447,11 @@ NLOHMANN_VIEW_ALWAYS_INLINE int locate_token(std::uint64_t w, int q, std::uint64
|
||||
return decided ? g : 3;
|
||||
}
|
||||
|
||||
/// all bits set if b, else none
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t mask_if(bool b) noexcept
|
||||
{
|
||||
return std::uint64_t{0} - static_cast<std::uint64_t>(b);
|
||||
}
|
||||
|
||||
/// a if b, else c, without a branch (the conditions of token_digits_1/2() are
|
||||
/// as good as random, and GCC compiled their ?: as branches: one misprediction
|
||||
/// per float of canada.json on x86-64)
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t select_if(bool b, std::uint64_t a, std::uint64_t c) noexcept
|
||||
{
|
||||
return c ^ ((a ^ c) & mask_if(b));
|
||||
}
|
||||
|
||||
/// the distance of a position (see locate_token()) from the biased point 32
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t biased_distance(std::uint64_t position) noexcept
|
||||
{
|
||||
constexpr std::uint64_t center = std::uint64_t{32} << 56u;
|
||||
// |position - center|, without a branch: negated (complemented, plus 1) if below
|
||||
const std::uint64_t below = mask_if(position < center);
|
||||
return ((position - center) ^ below) - below;
|
||||
return position >= center ? position - center : center - position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -490,10 +469,10 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_1(std::uint64_t w, std::u
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r10 * one); // x - (w - r10) (+ 32)
|
||||
const bool up = from > 37 * one; // (nearer to the multiple above)
|
||||
const bool shorter = biased_distance(from - (mask_if(up) & (10 * one))) < radius;
|
||||
const std::uint64_t a = w - r10 + (mask_if(up) & 10u);
|
||||
const bool shorter = biased_distance(from - (up ? 10 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r10 + (up ? 10u : 0u);
|
||||
const std::uint64_t b = w + ((offset + (one / 2)) >> 56u) - 32; // w rounded to x
|
||||
return select_if(shorter, a, b);
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -506,15 +485,15 @@ NLOHMANN_VIEW_ALWAYS_INLINE std::uint64_t token_digits_2(std::uint64_t w, std::u
|
||||
constexpr std::uint64_t one = std::uint64_t{1} << 56u;
|
||||
const std::uint64_t from = offset + (r100 * one);
|
||||
const bool up = from > 82 * one;
|
||||
const bool shorter = biased_distance(from - (mask_if(up) & (100 * one))) < radius;
|
||||
const std::uint64_t a = w - r100 + (mask_if(up) & 100u);
|
||||
const bool shorter = biased_distance(from - (up ? 100 * one : 0)) < radius;
|
||||
const std::uint64_t a = w - r100 + (up ? 100u : 0u);
|
||||
// x - (w - r10), in [-12, 21]: the multiple of 10 nearest to x is that one,
|
||||
// or one or two above, or one below
|
||||
const std::uint64_t y = offset + (r10 * one);
|
||||
const std::uint64_t steps = static_cast<std::uint64_t>(y > 37 * one) + static_cast<std::uint64_t>(y > 47 * one);
|
||||
const std::uint64_t steps = (y > 37 * one ? 1u : 0u) + (y > 47 * one ? 1u : 0u);
|
||||
const bool down = y < 27 * one;
|
||||
const std::uint64_t b = w - r10 + (10 * steps) - (mask_if(down) & 10u);
|
||||
return select_if(shorter, a, b);
|
||||
const std::uint64_t b = w - r10 + (10 * steps) - (down ? 10u : 0u);
|
||||
return shorter ? a : b;
|
||||
}
|
||||
|
||||
/// the four ASCII digits of x < 10^4, the first in the lowest byte (as
|
||||
@@ -646,7 +625,7 @@ class view_serializer
|
||||
{
|
||||
invalid_utf8(s, n.len); // (the scan stopped at ill-formed UTF-8)
|
||||
}
|
||||
write_escaped(s[i]); // a quote, a backslash, or a control character
|
||||
write_codepoint<false>(s[i], s + i, 1); // a quote, a backslash, or a control character
|
||||
++i;
|
||||
}
|
||||
m_out.put('"');
|
||||
@@ -1308,11 +1287,11 @@ class view_serializer
|
||||
// (the range excludes the values of bytes that are not digits, only
|
||||
// in a damaged image loaded with image_check::bounds)
|
||||
const int g = w - 1000000000000000u < 99000000000000000u ? locate_token(w, exponent[i], offset[i], radius[i]) : 3;
|
||||
batch.digits[i] = mask_if(g <= 0) & ((last_four[i] >> 16u) + 1);
|
||||
batch.digits[i] = g <= 0 ? (last_four[i] >> 16u) + 1 : 0;
|
||||
kind_1[count_1] = static_cast<std::uint8_t>(i);
|
||||
count_1 += static_cast<std::size_t>(g == 1);
|
||||
count_1 += g == 1 ? 1 : 0;
|
||||
kind_2[count_2] = static_cast<std::uint8_t>(i);
|
||||
count_2 += static_cast<std::size_t>(g == 2);
|
||||
count_2 += g == 2 ? 1 : 0;
|
||||
}
|
||||
// the digits to write, in one loop per kind (see mark())
|
||||
for (std::size_t k = 0; k != count_1; ++k)
|
||||
@@ -1394,10 +1373,20 @@ class view_serializer
|
||||
/// a float node as dump() writes it
|
||||
void write_float_node(const node& n)
|
||||
{
|
||||
// (only for types other than double: doubles go through write_double_ahead())
|
||||
write_float_node(n, fast_double {});
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::false_type /*other*/)
|
||||
{
|
||||
write_float(float_value<number_float_t>(m_doc, n));
|
||||
}
|
||||
|
||||
void write_float_node(const node& n, std::true_type /*double*/)
|
||||
{
|
||||
m_out.reserve(64);
|
||||
m_out.set_cursor(write_double_at(m_out.cursor(), n));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief (doubles) the float at n as dump() writes it, at w (64 bytes of room)
|
||||
|
||||
@@ -1492,11 +1481,10 @@ class view_serializer
|
||||
{
|
||||
const string_t dumped = BasicJsonType(string_t(reinterpret_cast<const char*>(s), n)).dump(); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
static_cast<void>(dumped);
|
||||
} // LCOV_EXCL_LINE (dump() throws: the callers pass only ill-formed UTF-8)
|
||||
}
|
||||
|
||||
/// write the escape sequence of a quote, a backslash or a control
|
||||
/// character (the only bytes a run of plain output stops at)
|
||||
void write_escaped(std::uint32_t codepoint)
|
||||
template<bool EnsureAscii>
|
||||
void write_codepoint(std::uint32_t codepoint, const unsigned char* bytes, std::size_t len)
|
||||
{
|
||||
switch (codepoint)
|
||||
{
|
||||
@@ -1524,7 +1512,20 @@ class view_serializer
|
||||
default:
|
||||
break;
|
||||
}
|
||||
write_u_escape(codepoint); // (a control character)
|
||||
if (codepoint <= 0x1F || (EnsureAscii && codepoint >= 0x7F))
|
||||
{
|
||||
if (codepoint <= 0xFFFF)
|
||||
{
|
||||
write_u_escape(codepoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_u_escape(0xD7C0u + (codepoint >> 10u));
|
||||
write_u_escape(0xDC00u + (codepoint & 0x3FFu));
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_out.put(reinterpret_cast<const char*>(bytes), len); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast) LCOV_EXCL_LINE (printable characters are copied in runs)
|
||||
}
|
||||
|
||||
void write_u_escape(std::uint32_t u)
|
||||
|
||||
@@ -29,13 +29,6 @@
|
||||
// NLOHMANN_VIEW_NO_TARGET_ATTRIBUTE (set by the GCC module interface, where GCC
|
||||
// ignores the target attribute and then rejects the SSSE3 intrinsics) keeps the
|
||||
// check portable without disabling the SSE2 code.
|
||||
//
|
||||
// The vector UTF-8 check (utf8_lookup4) is adapted from simdjson
|
||||
// (https://github.com/simdjson/simdjson, Daniel Lemire, Geoff Langdale, John
|
||||
// Keiser, and contributors, available under the Apache 2.0 and MIT licenses;
|
||||
// used here under the MIT license): the "lookup4" algorithm and tables of
|
||||
// J. Keiser and D. Lemire, "Validating UTF-8 In Less Than One Instruction Per
|
||||
// Byte" (2021).
|
||||
#if !defined(JSON_NO_SIMD) && defined(__aarch64__) && (defined(__GNUC__) || defined(__clang__)) && NLOHMANN_VIEW_LITTLE_ENDIAN
|
||||
#include <arm_neon.h>
|
||||
#define NLOHMANN_VIEW_NEON 1
|
||||
@@ -289,12 +282,9 @@ compares, and the UTF-8 check covers the bytes up to it. Returns where the
|
||||
string scan stops, like scan_string_run: before ill-formed UTF-8 and for the
|
||||
last bytes of the input, the bytes are checked one sequence at a time. Out of
|
||||
line, so that no constants of the check occupy registers in the parse loop.
|
||||
On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()). Not cloned: with
|
||||
the calls of accept()'s builder, GCC 16 would clone it without plain (always
|
||||
string_plain()), and calling the clone made json_document::parse() run 0.7-1.2 %
|
||||
more instructions.
|
||||
On x86-64, it is compiled for SSSE3 (see cpu_has_ssse3()).
|
||||
*/
|
||||
NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE NLOHMANN_VIEW_NOCLONE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept
|
||||
NLOHMANN_VIEW_SSSE3_TARGET NLOHMANN_VIEW_NOINLINE inline const unsigned char* scan_string_vector(const unsigned char* p, const unsigned char* e, const std::uint8_t* plain) noexcept
|
||||
{
|
||||
using lookup = utf8_lookup4<>;
|
||||
const unsigned char* block = p;
|
||||
|
||||
@@ -33,37 +33,12 @@ namespace view
|
||||
template<typename T>
|
||||
struct value_tag {};
|
||||
|
||||
/// whether basic_json reads T with a dedicated from_json() overload that does
|
||||
/// not accept a boolean: exactly number_integer_t, number_unsigned_t and
|
||||
/// number_float_t (the other arithmetic types convert a boolean to 0 or 1)
|
||||
template<typename T, typename BasicJsonType>
|
||||
struct rejects_boolean : std::integral_constant < bool,
|
||||
std::is_same<T, typename BasicJsonType::number_integer_t>::value
|
||||
|| std::is_same<T, typename BasicJsonType::number_unsigned_t>::value
|
||||
|| std::is_same<T, typename BasicJsonType::number_float_t>::value >
|
||||
{};
|
||||
|
||||
/// a boolean as T: 0 or 1
|
||||
template<typename T>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& /*d*/, const node& n, std::false_type /*rejects*/)
|
||||
{
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
}
|
||||
|
||||
/// a boolean as number_integer_t, number_unsigned_t or number_float_t: type_error.302, as in basic_json
|
||||
template<typename T>
|
||||
[[noreturn]] NLOHMANN_VIEW_ALWAYS_INLINE T boolean_as(const document_data& d, const node& n, std::true_type /*rejects*/)
|
||||
{
|
||||
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the number or boolean of a node converted to an arithmetic type
|
||||
|
||||
As basic_json's get<T>() for arithmetic types: integers and floats are
|
||||
converted with static_cast, booleans give 0 or 1 (but throw type_error.302 for
|
||||
number_integer_t, number_unsigned_t and number_float_t, as basic_json does),
|
||||
and other types throw type_error.302.
|
||||
converted with static_cast, booleans give 0 or 1, and other types throw
|
||||
type_error.302.
|
||||
*/
|
||||
template<typename T, typename BasicJsonType>
|
||||
NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const node& n)
|
||||
@@ -77,7 +52,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
||||
case value_t::number_float:
|
||||
return static_cast<T>(float_value<typename BasicJsonType::number_float_t>(d, n));
|
||||
case value_t::boolean:
|
||||
return boolean_as<T>(d, n, rejects_boolean<T, BasicJsonType> {});
|
||||
return static_cast<T>((n.flags & node_flags::is_true) != 0);
|
||||
case value_t::null:
|
||||
case value_t::object:
|
||||
case value_t::array:
|
||||
@@ -85,7 +60,7 @@ NLOHMANN_VIEW_ALWAYS_INLINE T arithmetic_value(const document_data& d, const nod
|
||||
case value_t::binary:
|
||||
case value_t::discarded:
|
||||
default:
|
||||
throw_type_error(error_site(&d, &n), exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
throw_type_error(exception_id::type_mismatch, "type must be number, but is ", value_type_name(static_cast<value_t>(n.kind)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +70,7 @@ std::vector<U, A> vector_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_array()))
|
||||
{
|
||||
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be array, but is ", v.type_name());
|
||||
}
|
||||
std::vector<U, A> r;
|
||||
r.reserve(v.size());
|
||||
@@ -113,7 +88,7 @@ Map map_value(const View& v)
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!v.is_object()))
|
||||
{
|
||||
throw_type_error(site_of(v), exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||
throw_type_error(exception_id::type_mismatch, "type must be object, but is ", v.type_name());
|
||||
}
|
||||
Map r;
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
|
||||
+97
-157
@@ -150,7 +150,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
// can be restored when json_pointer backwards compatibility is removed
|
||||
// friend ::nlohmann::json_pointer<StringType>;
|
||||
|
||||
template<typename BasicJsonType, typename InputType, bool DiscardValues>
|
||||
template<typename BasicJsonType, typename InputType>
|
||||
friend class ::nlohmann::detail::parser;
|
||||
friend ::nlohmann::detail::serializer<basic_json>;
|
||||
template<typename BasicJsonType>
|
||||
@@ -159,7 +159,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
friend class ::nlohmann::detail::binary_writer;
|
||||
template<typename BasicJsonType, typename InputType, typename SAX>
|
||||
friend class ::nlohmann::detail::binary_reader;
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect, typename LexerType>
|
||||
template<typename BasicJsonType, typename InputAdapterType, bool Collect>
|
||||
friend class ::nlohmann::detail::json_sax_dom_parser;
|
||||
template<typename BasicJsonType, typename InputAdapterType>
|
||||
friend class ::nlohmann::detail::json_sax_dom_binary_parser;
|
||||
@@ -183,11 +183,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
detail::parser_callback_t<basic_json>cb = nullptr,
|
||||
const bool allow_exceptions = true,
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false
|
||||
const bool ignore_trailing_commas = false,
|
||||
const bool discard_number_values = false
|
||||
)
|
||||
{
|
||||
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
|
||||
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas, discard_number_values);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -400,7 +401,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}};
|
||||
#elif defined(__GNUC__) || defined(__GNUG__)
|
||||
result["compiler"] = {{"family", "gcc"}, {"version", detail::concat<string_t>(
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.', // LCOV_EXCL_LINE
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC__)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_MINOR__)), '.',
|
||||
detail::to_string<string_t>(static_cast<std::size_t>(__GNUC_PATCHLEVEL__)))
|
||||
}
|
||||
@@ -543,10 +544,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
////////////////////////
|
||||
|
||||
JSON_PRIVATE_UNLESS_TESTED:
|
||||
// the type and value of a basic_json without its base class (defined
|
||||
// below); the destruction helpers of json_value use it as a stand-in
|
||||
struct data;
|
||||
|
||||
/*!
|
||||
@brief a JSON value
|
||||
|
||||
@@ -676,14 +673,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
case value_t::binary:
|
||||
binary = create<binary_t>();
|
||||
break;
|
||||
case value_t::null: // LCOV_EXCL_LINE
|
||||
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:
|
||||
break; // LCOV_EXCL_LINE
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,28 +720,27 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
||||
explicit json_value(array_t* value) noexcept : array(value) {}
|
||||
|
||||
private:
|
||||
// raw, allocation-free transfer of the type and value from src to
|
||||
// dst: no set_parents()/assert_invariant() (the former is
|
||||
// O(#children) per call under JSON_DIAGNOSTICS, which would make the
|
||||
// walk below quadratic); dst takes ownership, src is left as
|
||||
// value_t::null.
|
||||
static void take(data& dst, data& src) noexcept
|
||||
// raw, allocation-free transfer of m_data from src to dst: no
|
||||
// set_parents()/assert_invariant() (the former is O(#children) per
|
||||
// call under JSON_DIAGNOSTICS, which would make the walk below
|
||||
// quadratic); dst takes ownership, src is left as value_t::null.
|
||||
static void take(basic_json& dst, basic_json& src) noexcept
|
||||
{
|
||||
dst.m_type = src.m_type;
|
||||
dst.m_value = src.m_value;
|
||||
src.m_type = value_t::null;
|
||||
dst.m_data.m_type = src.m_data.m_type;
|
||||
dst.m_data.m_value = src.m_data.m_value;
|
||||
src.m_data.m_type = value_t::null;
|
||||
}
|
||||
|
||||
// true if v is not an array/object, or is an already-empty one
|
||||
static bool has_no_children(const data& v) noexcept
|
||||
static bool has_no_children(const basic_json& v) noexcept
|
||||
{
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_value.array->empty();
|
||||
return v.m_data.m_value.array->empty();
|
||||
}
|
||||
if (v.m_type == value_t::object)
|
||||
if (v.m_data.m_type == value_t::object)
|
||||
{
|
||||
return v.m_value.object->empty();
|
||||
return v.m_data.m_value.object->empty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -753,21 +749,21 @@ private:
|
||||
// its own, i.e. v can be freed in one go (its destructor then
|
||||
// destroys scalars and empty containers only, so it recurses at most
|
||||
// one level deep); stops at the first structured child it meets
|
||||
static bool all_children_leaves(const data& v) noexcept
|
||||
static bool all_children_leaves(const basic_json& v) noexcept
|
||||
{
|
||||
// array_t only needs forward iterators (see custom_array_type)
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
const auto& a = *v.m_value.array;
|
||||
const auto& a = *v.m_data.m_value.array;
|
||||
return std::all_of(a.begin(), a.end(), [](const basic_json & el)
|
||||
{
|
||||
return has_no_children(el.m_data);
|
||||
return has_no_children(el);
|
||||
});
|
||||
}
|
||||
const auto& o = *v.m_value.object;
|
||||
const auto& o = *v.m_data.m_value.object;
|
||||
return std::all_of(o.begin(), o.end(), [](const typename object_t::value_type & el)
|
||||
{
|
||||
return has_no_children(el.second.m_data);
|
||||
return has_no_children(el.second);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -799,30 +795,30 @@ private:
|
||||
|
||||
// the child of a non-empty array/object v that the walk in
|
||||
// destroy_container() continues with (see walk_child_it() above)
|
||||
static data& walk_child(data& v)
|
||||
static basic_json& walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
return v.m_value.array->back().m_data;
|
||||
return v.m_data.m_value.array->back();
|
||||
}
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_value.object)->second.m_data;
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
return walk_child_it(*v.m_data.m_value.object)->second;
|
||||
}
|
||||
|
||||
// removes walk_child(v) from a non-empty array/object v; this never
|
||||
// allocates, and since it is only ever called when that child is a
|
||||
// scalar or an already-empty array/object, destroying it never
|
||||
// recurses more than one level deep (see destroy() below)
|
||||
static void pop_walk_child(data& v)
|
||||
static void pop_walk_child(basic_json& v)
|
||||
{
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
v.m_value.array->pop_back();
|
||||
v.m_data.m_value.array->pop_back();
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
v.m_value.object->erase(walk_child_it(*v.m_value.object));
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
v.m_data.m_value.object->erase(walk_child_it(*v.m_data.m_value.object));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -831,23 +827,23 @@ private:
|
||||
// recurses at most one level deep); this is the same allocator-based
|
||||
// free the old recursive implementation used, just factored out so
|
||||
// every level of the walk in destroy() can share it
|
||||
static void free_container(data& v) noexcept
|
||||
static void free_container(basic_json& v) noexcept
|
||||
{
|
||||
JSON_ASSERT(has_no_children(v) || all_children_leaves(v));
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
AllocatorType<array_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.array, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.array);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.array, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
JSON_ASSERT(v.m_type == value_t::object);
|
||||
JSON_ASSERT(v.m_data.m_type == value_t::object);
|
||||
AllocatorType<object_t> alloc;
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_value.object, 1);
|
||||
std::allocator_traits<decltype(alloc)>::destroy(alloc, v.m_data.m_value.object);
|
||||
std::allocator_traits<decltype(alloc)>::deallocate(alloc, v.m_data.m_value.object, 1);
|
||||
}
|
||||
v.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
v.m_data.m_type = value_t::null; // avoid a double free if v is later destructed
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -878,15 +874,14 @@ public:
|
||||
|
||||
// This json_value is not itself a basic_json, so the top-level
|
||||
// container is first moved into a local stand-in ("cur"); a
|
||||
// default-constructed data has a null pointer in its m_value
|
||||
// (see data::m_value's initializer), so swapping it with *this
|
||||
// leaves this union's own pointer null, and it is never looked
|
||||
// at or freed a second time. The stand-ins are data, not
|
||||
// basic_json, so that destroying a value never constructs a
|
||||
// CustomBaseClass, whose constructor may allocate (#5995).
|
||||
data cur;
|
||||
cur.m_type = t;
|
||||
std::swap(cur.m_value, *this);
|
||||
// default-constructed basic_json has a null pointer in its
|
||||
// m_value (see data::m_value's initializer), so swapping it
|
||||
// with *this leaves this union's own pointer null, and it is
|
||||
// never looked at or freed a second time.
|
||||
basic_json cur;
|
||||
cur.m_data.m_type = t;
|
||||
using std::swap;
|
||||
swap(cur.m_data.m_value, *this);
|
||||
|
||||
destroy_bounded(cur, 0);
|
||||
}
|
||||
@@ -895,7 +890,7 @@ public:
|
||||
// The first recursion_depth_limit() levels are destroyed by plain
|
||||
// recursion, which is the fastest way and cannot throw; anything
|
||||
// nested deeper is handed to destroy_walk(), which uses no call stack.
|
||||
static void destroy_bounded(data& v, std::size_t depth) noexcept
|
||||
static void destroy_bounded(basic_json& v, std::size_t depth) noexcept
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= detail::recursion_depth_limit()))
|
||||
{
|
||||
@@ -903,23 +898,23 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.m_type == value_t::array)
|
||||
if (v.m_data.m_type == value_t::array)
|
||||
{
|
||||
for (auto& child : *v.m_value.array)
|
||||
for (auto& child : *v.m_data.m_value.array)
|
||||
{
|
||||
if (child.m_data.m_type == value_t::array || child.m_data.m_type == value_t::object)
|
||||
{
|
||||
destroy_bounded(child.m_data, depth + 1);
|
||||
destroy_bounded(child, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto& child : *v.m_value.object)
|
||||
for (auto& child : *v.m_data.m_value.object)
|
||||
{
|
||||
if (child.second.m_data.m_type == value_t::array || child.second.m_data.m_type == value_t::object)
|
||||
{
|
||||
destroy_bounded(child.second.m_data, depth + 1);
|
||||
destroy_bounded(child.second, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -946,15 +941,18 @@ public:
|
||||
// array/object, which neither allocates nor recurses more
|
||||
// than one level deep.
|
||||
//
|
||||
// Like destroy_container(), the walk keeps the containers it
|
||||
// holds in data stand-ins (cur, prev, gp, tmp), which never
|
||||
// construct a CustomBaseClass.
|
||||
static void destroy_walk(data& top) noexcept
|
||||
// This json_value is not itself a basic_json, so the
|
||||
// top-level container is first moved into a local stand-in
|
||||
// ("cur"); a default-constructed basic_json has a null
|
||||
// pointer in its m_value (see data::m_value's initializer),
|
||||
// so swapping it with *this leaves this union's own pointer
|
||||
// null, and it is never looked at or freed a second time.
|
||||
static void destroy_walk(basic_json& top) noexcept
|
||||
{
|
||||
data cur;
|
||||
basic_json cur;
|
||||
take(cur, top);
|
||||
|
||||
data prev; // value_t::null: no parent
|
||||
basic_json prev; // value_t::null: no parent
|
||||
|
||||
// true right after cur was entered: its children have not been
|
||||
// looked at yet. If none of them has children of its own, cur is
|
||||
@@ -968,7 +966,7 @@ public:
|
||||
fresh = false;
|
||||
if (has_no_children(cur) || (fresh_now && all_children_leaves(cur)))
|
||||
{
|
||||
if (prev.m_type == value_t::null)
|
||||
if (prev.m_data.m_type == value_t::null)
|
||||
{
|
||||
free_container(cur);
|
||||
return; // back at the top with nothing left to do
|
||||
@@ -977,7 +975,7 @@ public:
|
||||
// ascend: detach the grandparent link from prev's
|
||||
// walk_child() slot, drop that (now null) slot, free cur
|
||||
// (it is empty), then move up one level
|
||||
data gp;
|
||||
basic_json gp;
|
||||
take(gp, walk_child(prev));
|
||||
pop_walk_child(prev);
|
||||
|
||||
@@ -988,7 +986,7 @@ public:
|
||||
continue;
|
||||
}
|
||||
|
||||
data& cur_child_ref = walk_child(cur);
|
||||
basic_json& cur_child_ref = walk_child(cur);
|
||||
|
||||
if (has_no_children(cur_child_ref))
|
||||
{
|
||||
@@ -1000,7 +998,7 @@ public:
|
||||
// descend into the non-empty child, reversing the
|
||||
// link: its slot takes over prev, and the child becomes
|
||||
// the new cur
|
||||
data tmp;
|
||||
basic_json tmp;
|
||||
take(tmp, cur_child_ref);
|
||||
take(cur_child_ref, prev);
|
||||
take(prev, cur);
|
||||
@@ -1037,14 +1035,14 @@ public:
|
||||
destroy_container(t);
|
||||
break;
|
||||
|
||||
case value_t::null: // LCOV_EXCL_LINE
|
||||
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:
|
||||
break; // LCOV_EXCL_LINE
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1217,9 +1215,9 @@ public:
|
||||
return is_array() ? array_capacity() : object_capacity();
|
||||
}
|
||||
#else
|
||||
static constexpr std::size_t container_capacity() noexcept // LCOV_EXCL_LINE
|
||||
static constexpr std::size_t container_capacity() noexcept
|
||||
{
|
||||
return detail::unknown_size(); // LCOV_EXCL_LINE
|
||||
return detail::unknown_size();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1405,7 +1403,7 @@ public:
|
||||
/// into this switch is worth a few percent when copying a value made
|
||||
/// mostly of numbers
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static void copy_leaf_value(const basic_json& src, basic_json& dst) // LCOV_EXCL_LINE
|
||||
static void copy_leaf_value(const basic_json& src, basic_json& dst)
|
||||
{
|
||||
switch (src.m_data.m_type)
|
||||
{
|
||||
@@ -1579,7 +1577,7 @@ public:
|
||||
auto src_it = src_object.cbegin();
|
||||
for (auto& element : *dst.m_data.m_value.object)
|
||||
{
|
||||
if (JSON_HEDLEY_LIKELY(copy_keys_equal(src_it->first, element.first)))
|
||||
if (JSON_HEDLEY_LIKELY(src_it != src_object.cend() && copy_keys_equal(src_it->first, element.first)))
|
||||
{
|
||||
copy_shallow(src_it->second, element.second, worklist);
|
||||
++src_it;
|
||||
@@ -3119,9 +3117,7 @@ public:
|
||||
const bool ignore_comments,
|
||||
const bool ignore_trailing_commas)
|
||||
{
|
||||
// the parser and lexer that discard the values of tokens (see lexer)
|
||||
return ::nlohmann::detail::parser<basic_json, typename std::decay<InputAdapterType>::type, true>(std::forward<InputAdapterType>(adapter),
|
||||
nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
|
||||
return parser(std::forward<InputAdapterType>(adapter), nullptr, false, ignore_comments, ignore_trailing_commas, true).accept(true);
|
||||
}
|
||||
|
||||
/// generate SAX events from the input behind an already created adapter
|
||||
@@ -4917,30 +4913,6 @@ public:
|
||||
return {it, res.second};
|
||||
}
|
||||
|
||||
private:
|
||||
/// Sets the parents of a value when it goes out of scope, also when an
|
||||
/// exception is thrown: a container insertion that throws may already
|
||||
/// have added elements, whose parents must be set for the value to stay
|
||||
/// valid (basic exception guarantee). Does nothing without JSON_DIAGNOSTICS.
|
||||
class set_parents_on_exit
|
||||
{
|
||||
public:
|
||||
explicit set_parents_on_exit(basic_json* v) noexcept : m_value(v) {}
|
||||
set_parents_on_exit(const set_parents_on_exit&) = delete;
|
||||
set_parents_on_exit& operator=(const set_parents_on_exit&) = delete;
|
||||
set_parents_on_exit(set_parents_on_exit&&) = delete;
|
||||
set_parents_on_exit& operator=(set_parents_on_exit&&) = delete;
|
||||
|
||||
~set_parents_on_exit()
|
||||
{
|
||||
m_value->set_parents();
|
||||
}
|
||||
|
||||
private:
|
||||
basic_json* m_value;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Helper for insertion of an iterator
|
||||
/// @note: This uses std::distance to support GCC 4.8,
|
||||
/// see https://github.com/nlohmann/json/pull/1257
|
||||
@@ -4950,7 +4922,6 @@ public:
|
||||
iterator result(this);
|
||||
JSON_ASSERT(m_data.m_value.array != nullptr);
|
||||
|
||||
const set_parents_on_exit parents(this);
|
||||
auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);
|
||||
m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);
|
||||
result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;
|
||||
@@ -4959,6 +4930,7 @@ public:
|
||||
// result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);
|
||||
// but the return value of insert is missing in GCC 4.8, so it is written this way instead.
|
||||
|
||||
set_parents();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -4978,16 +4950,8 @@ public:
|
||||
throw_iterator_from_other_value(this);
|
||||
}
|
||||
|
||||
// vector::insert has no effects if copying a single element to the
|
||||
// end throws; elsewhere, it may already have shifted the elements, so
|
||||
// copy first and insert by move, which cannot throw but by allocating
|
||||
if (pos.m_it.array_iterator == m_data.m_value.array->end())
|
||||
{
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
basic_json tmp(val);
|
||||
return insert_iterator(pos, std::move(tmp));
|
||||
// insert to array and return iterator
|
||||
return insert_iterator(pos, val);
|
||||
}
|
||||
|
||||
/// @brief inserts element into array
|
||||
@@ -5119,8 +5083,8 @@ public:
|
||||
detail::throw_literal<invalid_iterator>(detail::exception_id::iterator_from_other_value, "iterators first and last must point to objects", this);
|
||||
}
|
||||
|
||||
const set_parents_on_exit parents(this);
|
||||
m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);
|
||||
set_parents();
|
||||
}
|
||||
|
||||
/// @brief updates a JSON object from another object, overwriting existing keys
|
||||
@@ -5388,7 +5352,8 @@ public:
|
||||
/// @name lexicographical comparison operators
|
||||
/// @{
|
||||
|
||||
// the same-type cases for non-owning scalars, used by JSON_IMPLEMENT_SCALAR_FAST
|
||||
// the same-type cases for non-owning scalars, shared by JSON_IMPLEMENT_OPERATOR
|
||||
// and JSON_IMPLEMENT_SCALAR_FAST
|
||||
#define JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
||||
case value_t::null: \
|
||||
return (null_result); \
|
||||
@@ -5410,7 +5375,7 @@ public:
|
||||
// because any negative signed value is smaller than any unsigned value.
|
||||
// Otherwise, the non-negative signed value is cast to unsigned before the
|
||||
// comparison to avoid wraparound.
|
||||
#define JSON_IMPLEMENT_OPERATOR(op, unordered_result, default_result, deep_result, may_descend) \
|
||||
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result, deep_result, may_descend) \
|
||||
const auto lhs_type = lhs.type(); \
|
||||
const auto rhs_type = rhs.type(); \
|
||||
\
|
||||
@@ -5438,15 +5403,8 @@ public:
|
||||
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||
} \
|
||||
\
|
||||
/* same-type scalars never get here: the inline fast path of the operator handles them */ \
|
||||
case value_t::null: \
|
||||
case value_t::boolean: \
|
||||
case value_t::number_integer: \
|
||||
case value_t::number_unsigned: \
|
||||
case value_t::number_float: \
|
||||
JSON_ASSERT(false); /* NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) */ \
|
||||
return (unordered_result); \
|
||||
\
|
||||
JSON_IMPLEMENT_SCALAR_CASES(op, null_result) \
|
||||
\
|
||||
case value_t::string: \
|
||||
return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string); \
|
||||
\
|
||||
@@ -5486,7 +5444,7 @@ public:
|
||||
? (number_integer_t(-1) op number_integer_t(1)) \
|
||||
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
|
||||
} \
|
||||
else if(lhs_type == value_t::discarded || rhs_type == value_t::discarded)\
|
||||
else if(compares_unordered(lhs, rhs))\
|
||||
{\
|
||||
return (unordered_result);\
|
||||
}\
|
||||
@@ -5554,7 +5512,7 @@ public:
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||
#endif
|
||||
JSON_IMPLEMENT_OPERATOR( ==, false, false,
|
||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
||||
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||
@@ -5568,6 +5526,7 @@ public:
|
||||
// default_result is used if we cannot compare values. In that case,
|
||||
// we compare types.
|
||||
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
|
||||
std::partial_ordering::equivalent,
|
||||
std::partial_ordering::unordered,
|
||||
lhs_type <=> rhs_type, // *NOPAD*
|
||||
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
|
||||
@@ -5579,7 +5538,7 @@ public:
|
||||
// default_result is used if we cannot compare values. In that case,
|
||||
// we compare types. Note we have to call the operator explicitly,
|
||||
// because MSVC has problems otherwise.
|
||||
JSON_IMPLEMENT_OPERATOR( <, false, operator<(lhs_type, rhs_type),
|
||||
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
|
||||
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
|
||||
}
|
||||
#endif
|
||||
@@ -5594,7 +5553,7 @@ public:
|
||||
/// their inlined code at each call site small. Binary values are
|
||||
/// rare and left to equal_general().
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept // LCOV_EXCL_LINE
|
||||
static int equal_leaf(const_reference lhs, const_reference rhs) noexcept
|
||||
{
|
||||
#ifdef __GNUC__
|
||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||
@@ -6903,7 +6862,7 @@ public:
|
||||
basic_json result(value_t::object);
|
||||
json_pointer::flatten("", *this, result);
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @brief unflatten a previously flattened JSON value
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/unflatten/
|
||||
@@ -6991,25 +6950,6 @@ public:
|
||||
/// @sa https://json.nlohmann.me/api/basic_json/patch/
|
||||
void patch_inplace(const basic_json& json_patch)
|
||||
{
|
||||
// the operations below change *this while iterating json_patch: apply
|
||||
// a copy when a value is applied as patch to itself or, with
|
||||
// JSON_DIAGNOSTICS, to a value stored inside it
|
||||
// NOLINTNEXTLINE(misc-const-correctness): only the JSON_DIAGNOSTICS build modifies it
|
||||
bool patch_aliases_this = (&json_patch == this);
|
||||
#if JSON_DIAGNOSTICS
|
||||
for (const basic_json* p = json_patch.m_parent; !patch_aliases_this && p != nullptr; p = p->m_parent)
|
||||
{
|
||||
patch_aliases_this = (p == this);
|
||||
}
|
||||
#endif
|
||||
if (JSON_HEDLEY_UNLIKELY(patch_aliases_this))
|
||||
{
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): *this is modified below
|
||||
const basic_json json_patch_copy = json_patch;
|
||||
patch_inplace(json_patch_copy);
|
||||
return;
|
||||
}
|
||||
|
||||
basic_json& result = *this;
|
||||
// the valid JSON Patch operations
|
||||
enum class patch_operations {add, remove, replace, move, copy, test, invalid};
|
||||
@@ -7616,7 +7556,7 @@ public:
|
||||
result.append(path.data(), path.size());
|
||||
diff_append_key(result, key);
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @brief the path of the element @a index of the value at @a path
|
||||
static string_t diff_index_path(const string_t& path, const std::size_t index)
|
||||
@@ -7626,7 +7566,7 @@ public:
|
||||
result.append(path.data(), path.size());
|
||||
diff_append_index(result, index);
|
||||
return result;
|
||||
} // LCOV_EXCL_LINE (exception cleanup only)
|
||||
}
|
||||
|
||||
/// @brief append the "remove" operations for the elements of array
|
||||
/// @a source from @a index on, and the "add" operations for the
|
||||
@@ -7786,7 +7726,7 @@ public:
|
||||
// operations
|
||||
if (source == target)
|
||||
{
|
||||
return false; // LCOV_EXCL_LINE (equal objects with the same members in the same order return early)
|
||||
return false;
|
||||
}
|
||||
|
||||
// slow path: the common keys are in a different relative
|
||||
|
||||
@@ -285,7 +285,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a string argument with ", type_name());
|
||||
}
|
||||
return lookup(key);
|
||||
}
|
||||
@@ -311,7 +311,7 @@ class basic_json_view
|
||||
{
|
||||
return {};
|
||||
}
|
||||
detail::view::throw_type_error(site(), detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::subscript_wrong_type, "cannot use operator[] with a numeric argument with ", type_name());
|
||||
}
|
||||
return idx < m_node->len ? element(idx) : basic_json_view();
|
||||
}
|
||||
@@ -344,12 +344,12 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
if (NLOHMANN_VIEW_UNLIKELY(r.is_discarded()))
|
||||
{
|
||||
detail::view::throw_key_not_found(site(), std::string(key.data(), key.size()));
|
||||
detail::view::throw_key_not_found(std::string(key.data(), key.size()));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -370,11 +370,11 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_array()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::at_wrong_type, "cannot use at() with ", type_name());
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(idx >= m_node->len))
|
||||
{
|
||||
detail::view::throw_array_index_out_of_range(site(), idx);
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return element(idx);
|
||||
}
|
||||
@@ -402,7 +402,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = lookup(key);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -412,7 +412,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_object()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
// (the default is made only if there is no such member)
|
||||
const basic_json_view r = lookup(key);
|
||||
@@ -427,7 +427,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? default_value : r.template get<T>();
|
||||
@@ -437,7 +437,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_structured()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::value_wrong_type, "cannot use value() with ", type_name());
|
||||
}
|
||||
const basic_json_view r = detail::view::resolve_pointer(*this, detail::json_pointer_access::reference_tokens(ptr), detail::view::pointer_mode::value);
|
||||
return r.is_discarded() ? string_t(default_value) : r.template get<string_t>();
|
||||
@@ -450,7 +450,7 @@ class basic_json_view
|
||||
const iterator it = begin();
|
||||
if (NLOHMANN_VIEW_UNLIKELY(it == end()))
|
||||
{
|
||||
detail::view::throw_invalid_iterator(site(), detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||
detail::view::throw_invalid_iterator(detail::exception_id::iterator_value_unavailable, "cannot get value");
|
||||
}
|
||||
return *it;
|
||||
}
|
||||
@@ -606,7 +606,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_string()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be string, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), m_node->len};
|
||||
}
|
||||
@@ -618,7 +618,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_number()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return {m_doc->str(*m_node), detail::view::number_length(*m_node)};
|
||||
}
|
||||
@@ -763,20 +763,12 @@ class basic_json_view
|
||||
template<typename, view_mode> friend class basic_json_document;
|
||||
template<typename, view_mode> friend class basic_json_view;
|
||||
template<typename, typename> friend class detail::view::editor;
|
||||
template<typename, typename> friend class detail::view::view_side;
|
||||
friend struct detail::view::view_access;
|
||||
friend iterator;
|
||||
|
||||
basic_json_view(const document_data* d, const node* n) noexcept
|
||||
: m_doc(d), m_node(n)
|
||||
{}
|
||||
|
||||
/// this value as the context of an exception
|
||||
detail::view::error_site site() const noexcept
|
||||
{
|
||||
return {m_doc, m_node};
|
||||
}
|
||||
|
||||
using json_side_t = detail::view::json_side<BasicJsonType, string_view_t>;
|
||||
|
||||
static detail::view::view_side<BasicJsonType, basic_json_view> side(const basic_json_view& v) noexcept
|
||||
@@ -883,7 +875,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_boolean()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be boolean, but is ", type_name());
|
||||
}
|
||||
return (m_node->flags & detail::view::node_flags::is_true) != 0;
|
||||
}
|
||||
@@ -893,7 +885,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(m_node == nullptr))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be number, but is ", type_name());
|
||||
}
|
||||
return detail::view::arithmetic_value<T, BasicJsonType>(*m_doc, *m_node);
|
||||
}
|
||||
@@ -902,7 +894,7 @@ class basic_json_view
|
||||
{
|
||||
if (NLOHMANN_VIEW_UNLIKELY(!is_null()))
|
||||
{
|
||||
detail::view::throw_type_error(site(), detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||
detail::view::throw_type_error(detail::exception_id::type_mismatch, "type must be null, but is ", type_name());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1067,7 +1059,7 @@ class basic_json_document
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.read_range(first, last, error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
|
||||
d.read_range(first, last, allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -1082,7 +1074,7 @@ class basic_json_document
|
||||
basic_json_document d;
|
||||
std::string buf;
|
||||
append_input(buf, std::forward<InputType>(input));
|
||||
d.build_owned(std::move(buf), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas);
|
||||
d.build_owned(std::move(buf), allow_exceptions, ignore_comments, ignore_trailing_commas);
|
||||
return d;
|
||||
}
|
||||
|
||||
@@ -1095,8 +1087,7 @@ class basic_json_document
|
||||
static bool accept(InputType&& input, const bool ignore_comments = false, const bool ignore_trailing_commas = false)
|
||||
{
|
||||
basic_json_document d;
|
||||
d.read_kind(std::forward<InputType>(input), on_error::validate, ignore_comments, ignore_trailing_commas,
|
||||
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
|
||||
d.read(std::forward<InputType>(input), false, ignore_comments, ignore_trailing_commas);
|
||||
return !d.is_discarded();
|
||||
}
|
||||
|
||||
@@ -1112,7 +1103,7 @@ class basic_json_document
|
||||
const bool ignore_comments = false,
|
||||
const bool ignore_trailing_commas = false)
|
||||
{
|
||||
read_kind(std::forward<InputType>(input), error_mode(allow_exceptions), ignore_comments, ignore_trailing_commas,
|
||||
read_kind(std::forward<InputType>(input), allow_exceptions, ignore_comments, ignore_trailing_commas,
|
||||
std::integral_constant<detail::view::input_kind, detail::view::classify_input<InputType>::value> {});
|
||||
}
|
||||
|
||||
@@ -1317,7 +1308,7 @@ class basic_json_document
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type set(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().set(array, index(array, idx), std::forward<V>(value));
|
||||
return editor().set(array, index(idx), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// set the value at a JSON pointer: its parent must exist; an object
|
||||
@@ -1354,7 +1345,7 @@ class basic_json_document
|
||||
const std::size_t idx = pointer_index(token);
|
||||
if (idx >= 0xFFFFFFFFu)
|
||||
{
|
||||
detail::view::throw_array_index_out_of_range(detail::view::site_of(parent), idx);
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
for (std::size_t i = 0; i < idx; ++i)
|
||||
{
|
||||
@@ -1388,7 +1379,7 @@ class basic_json_document
|
||||
template<typename I, typename V, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
view_type insert(view_type array, I idx, V && value)
|
||||
{
|
||||
return editor().insert(array, index(array, idx), std::forward<V>(value));
|
||||
return editor().insert(array, index(idx), std::forward<V>(value));
|
||||
}
|
||||
|
||||
/// remove all members with this key; returns their number
|
||||
@@ -1401,7 +1392,7 @@ class basic_json_document
|
||||
template<typename I, typename std::enable_if<detail::view::is_non_bool_integral<I>::value, int>::type = 0>
|
||||
void erase(view_type array, I idx)
|
||||
{
|
||||
editor().erase(array, index(array, idx));
|
||||
editor().erase(array, index(idx));
|
||||
}
|
||||
|
||||
/// remove the value at a JSON pointer; returns the number of removed
|
||||
@@ -1435,25 +1426,25 @@ class basic_json_document
|
||||
return detail::view::editor<BasicJsonType, view_type>(*m_data);
|
||||
}
|
||||
|
||||
/// an index into @a array (out_of_range.401 if negative)
|
||||
/// an index (out_of_range.401 if negative)
|
||||
template<typename I>
|
||||
static std::size_t index(const view_type& array, I idx)
|
||||
static std::size_t index(I idx)
|
||||
{
|
||||
return index(array, idx, std::is_signed<I> {});
|
||||
return index(idx, std::is_signed<I> {});
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(const view_type& array, I idx, std::true_type /*signed*/)
|
||||
static std::size_t index(I idx, std::true_type /*signed*/)
|
||||
{
|
||||
if (idx < 0)
|
||||
{
|
||||
detail::view::throw_array_index_out_of_range(detail::view::site_of(array), idx);
|
||||
detail::view::throw_array_index_out_of_range(idx);
|
||||
}
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
|
||||
template<typename I>
|
||||
static std::size_t index(const view_type& /*array*/, I idx, std::false_type /*unsigned*/)
|
||||
static std::size_t index(I idx, std::false_type /*unsigned*/)
|
||||
{
|
||||
return static_cast<std::size_t>(idx);
|
||||
}
|
||||
@@ -1471,22 +1462,6 @@ class basic_json_document
|
||||
return idx;
|
||||
}
|
||||
|
||||
/// what a failed parse does
|
||||
enum class on_error : std::uint8_t
|
||||
{
|
||||
discard, ///< the document is discarded
|
||||
raise, ///< parse_error is thrown
|
||||
validate ///< accept(): the document is discarded, and no node is stored while parsing
|
||||
};
|
||||
|
||||
static on_error error_mode(bool allow_exceptions) noexcept
|
||||
{
|
||||
return allow_exceptions ? on_error::raise : on_error::discard;
|
||||
}
|
||||
|
||||
/// NulIsEnd of the builder: a null byte ends the input unless strict_nul_handling
|
||||
static constexpr bool nul_is_end = !detail::abi_config::strict_nul_handling;
|
||||
|
||||
/// create the storage (sized for the input) on first use
|
||||
void ensure_data(const char* src, std::size_t size)
|
||||
{
|
||||
@@ -1498,7 +1473,7 @@ class basic_json_document
|
||||
|
||||
/// parse a buffer the document takes ownership of (it replaces the
|
||||
/// previous owned copy)
|
||||
void build_owned(std::string&& buf, on_error allow_exceptions, bool comments, bool trailing_commas)
|
||||
void build_owned(std::string&& buf, bool allow_exceptions, bool comments, bool trailing_commas)
|
||||
{
|
||||
ensure_data(buf.data(), buf.size());
|
||||
m_data->owned = std::move(buf);
|
||||
@@ -1508,7 +1483,7 @@ class basic_json_document
|
||||
/// parse a copy of the input; the copy is appended to the cleared owned
|
||||
/// buffer, so that its capacity is reused
|
||||
template<typename T>
|
||||
void build_copy(T&& input, on_error allow_exceptions, bool comments, bool trailing_commas)
|
||||
void build_copy(T&& input, bool allow_exceptions, bool comments, bool trailing_commas)
|
||||
{
|
||||
if (!m_data)
|
||||
{
|
||||
@@ -1529,7 +1504,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
/// sentinel: src[size] is readable and 0 (std::string, C strings)
|
||||
void build(const char* src, std::size_t size, on_error allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
|
||||
void build(const char* src, std::size_t size, bool allow_exceptions, bool comments, bool trailing_commas, bool owned, bool sentinel)
|
||||
{
|
||||
ensure_data(src, size);
|
||||
document_data& d = *m_data;
|
||||
@@ -1556,8 +1531,7 @@ class basic_json_document
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = detail::view::build<typename BasicJsonType::number_float_t, nul_is_end>(d, src, size, comments, trailing_commas, sentinel,
|
||||
allow_exceptions == on_error::validate, failure);
|
||||
ok = detail::view::build < typename BasicJsonType::number_float_t, !detail::abi_config::strict_nul_handling > (d, src, size, comments, trailing_commas, sentinel, failure);
|
||||
}
|
||||
if (NLOHMANN_VIEW_LIKELY(ok))
|
||||
{
|
||||
@@ -1570,7 +1544,7 @@ class basic_json_document
|
||||
return;
|
||||
}
|
||||
std::vector<std::uint32_t>().swap(d.large_objects);
|
||||
if (allow_exceptions == on_error::raise)
|
||||
if (allow_exceptions)
|
||||
{
|
||||
detail::view::throw_parse_failure<BasicJsonType>(failure, src, size, comments, trailing_commas);
|
||||
}
|
||||
@@ -1578,13 +1552,13 @@ class basic_json_document
|
||||
|
||||
// --- input dispatch (see detail::view::input_kind) ---
|
||||
|
||||
void read_kind(std::string&& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
|
||||
void read_kind(std::string&& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::move_string> /*unused*/)
|
||||
{
|
||||
build_owned(std::move(s), ae, c, tc);
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
|
||||
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::c_string> /*unused*/)
|
||||
{
|
||||
if (s == nullptr)
|
||||
{
|
||||
@@ -1598,7 +1572,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
template<typename Array>
|
||||
void read_kind(Array& a, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
|
||||
void read_kind(Array& a, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::char_array> /*unused*/)
|
||||
{
|
||||
using CharT = typename std::remove_cv<typename std::remove_extent<Array>::type>::type;
|
||||
const std::size_t n = std::extent<Array>::value;
|
||||
@@ -1609,7 +1583,7 @@ class basic_json_document
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
|
||||
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::borrow_range> /*unused*/)
|
||||
{
|
||||
// std::basic_string guarantees data()[size()] == 0: use it as sentinel
|
||||
const auto size = static_cast<std::size_t>(s.size());
|
||||
@@ -1618,13 +1592,13 @@ class basic_json_document
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void read_kind(const T& s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
|
||||
void read_kind(const T& s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::copy_range> /*unused*/)
|
||||
{
|
||||
build_copy(s, ae, c, tc);
|
||||
}
|
||||
|
||||
template<typename CharT>
|
||||
void read_kind(CharT* s, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
|
||||
void read_kind(CharT* s, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::wide_c_string> /*unused*/)
|
||||
{
|
||||
if (s == nullptr)
|
||||
{
|
||||
@@ -1636,13 +1610,13 @@ class basic_json_document
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void read_kind(T&& input, on_error ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
|
||||
void read_kind(T&& input, bool ae, bool c, bool tc, std::integral_constant<input_kind, input_kind::adapter> /*unused*/)
|
||||
{
|
||||
build_copy(std::forward<T>(input), ae, c, tc);
|
||||
}
|
||||
|
||||
template<typename IteratorType>
|
||||
void read_range(IteratorType first, IteratorType last, on_error ae, bool c, bool tc)
|
||||
void read_range(IteratorType first, IteratorType last, bool ae, bool c, bool tc)
|
||||
{
|
||||
using value_type = typename std::remove_cv<typename std::iterator_traits<IteratorType>::value_type>::type;
|
||||
// borrow the range where the library's input adapter scans it as one
|
||||
@@ -1653,14 +1627,14 @@ class basic_json_document
|
||||
}
|
||||
|
||||
template<typename IteratorType>
|
||||
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
|
||||
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::true_type /*contiguous bytes*/)
|
||||
{
|
||||
const auto size = static_cast<std::size_t>(std::distance(first, last));
|
||||
build(size == 0 ? "" : reinterpret_cast<const char*>(&*first), size, ae, c, tc, false, size == 0); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
}
|
||||
|
||||
template<typename IteratorType>
|
||||
void read_range_impl(IteratorType first, IteratorType last, on_error ae, bool c, bool tc, std::false_type /*other*/)
|
||||
void read_range_impl(IteratorType first, IteratorType last, bool ae, bool c, bool tc, std::false_type /*other*/)
|
||||
{
|
||||
build_copy(detail::view::make_adapter_input(detail::input_adapter(first, last)), ae, c, tc);
|
||||
}
|
||||
@@ -1705,11 +1679,9 @@ class basic_json_document
|
||||
{
|
||||
// (as before: the library's adapter decides what a null stream yields)
|
||||
detail::view::append_adapter(buf, detail::input_adapter(file));
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
detail::view::append_file(buf, file);
|
||||
}
|
||||
detail::view::append_file(buf, file);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Generated
-11880
File diff suppressed because it is too large.
Load diff
Generated
+672
-2225
File diff suppressed because it is too large.
Load diff
Generated
+1001
-2812
File diff suppressed because it is too large.
Load diff
@@ -21,10 +21,6 @@ python3 tests/benchmarks/json_view/compare.py --data <json_test_data directory>
|
||||
- The other libraries come from the system: pkg-config, or Homebrew (`brew install yyjson simdjson boost`). With
|
||||
`--download`, pinned releases are downloaded instead and checked against their SHA-256. Without Boost headers (or
|
||||
with `--no-boost`), the Boost.JSON columns are skipped, and the results say so.
|
||||
- `--aligned` additionally builds and runs everything with all functions aligned to 64 bytes (`-mllvm
|
||||
-align-all-functions=6` for Clang, `-falign-functions=64 -falign-loops=64` for GCC), and reports both result
|
||||
tables as "default build" and "aligned build". Code layout alone can move a result by a few percent; a difference
|
||||
that appears in only one of the two tables is a layout artifact. Off by default, which doubles the run time.
|
||||
- `--corpus file...` adds files to the corpus benchmark, e.g. those of
|
||||
[simdjson-data](https://github.com/simdjson/simdjson-data) or the
|
||||
[yyjson benchmark](https://github.com/ibireme/yyjson_benchmark).
|
||||
@@ -43,7 +39,7 @@ where `json_view` stands on another architecture; they are not meant for publica
|
||||
|
||||
## What is measured
|
||||
|
||||
`bench_view.cpp` runs these workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||
`bench_view.cpp` runs four workloads on twitter, citm_catalog, canada, jeopardy, a single tweet (`status`), and a
|
||||
JSON-RPC request (`rpc`):
|
||||
|
||||
| workload | what it does |
|
||||
@@ -52,7 +48,6 @@ JSON-RPC request (`rpc`):
|
||||
| traverse | parse, then visit every value, convert every number, touch every string and key |
|
||||
| select | parse, then read a few fields per record (e.g. id, user name, and retweet count of each tweet) |
|
||||
| dump | serialize a parsed document (compact) |
|
||||
| parse+dump | build a fresh document, then serialize it (compact), in one timed call |
|
||||
|
||||
`bench_corpus.cpp` runs parse, traverse, and dump on any list of files, so that no library is tuned to a handful of
|
||||
documents. Its dump also writes the numbers as they are in the input: `json_view` with `number_format::source`, and
|
||||
@@ -77,32 +72,11 @@ call follows an untimed call of the same engine: otherwise the engine after `jso
|
||||
cleaning up the tens of thousands of nodes `json::parse` just freed (with glibc, this made `json_view` look 1.7 times
|
||||
slower on citm_catalog traverse).
|
||||
|
||||
`json_view` is an index into the source text, not a tree of owning nodes. Its like-for-like peers are yyjson and
|
||||
simdjson DOM (immutable, indexed, no per-node allocation). Boost.JSON and `json::parse` build mutable, owning DOMs and
|
||||
are reference points for what that costs, not equals; simdjson On-Demand is lazy and forward-only, so it is the other
|
||||
way round: it has no random access to pay for. All engines validate UTF-8 and syntax by default.
|
||||
|
||||
Number conversion differs: `json_view` converts integers while parsing but converts floating-point numbers lazily,
|
||||
when they are read or dumped; yyjson (default flags) and simdjson DOM convert every number while parsing. The cost of
|
||||
float conversion therefore appears in the traverse and dump rows for `json_view`, and in the parse row for the others.
|
||||
For float-heavy documents (canada), the fair reading is a sum of parse and use: the `traverse` workload already
|
||||
includes the parse, and `parse+dump` measures a fresh document and its dump in one timed call; the `dump` row alone
|
||||
leaves the conversion in `json_view` and out of the others. Eager conversion at parse time was prototyped as an opt-in
|
||||
and measured: canada traverse (with its parse) was 4-7 % faster, but parse+dump 11-30 % slower and parse 36-90 % slower. It was
|
||||
dropped, so lazy conversion is the only mode.
|
||||
|
||||
`json_view` can also be edited: `json_editable_document` has the same read API, and the `(editable)` rows in
|
||||
`bench_view` measure it on the same workloads. They are the fair counterpart of Boost.JSON and `json::parse`, which are
|
||||
mutable too; the plain `json_view` rows are the counterpart of yyjson and simdjson DOM, which are not. `bench_edit` adds
|
||||
the read-modify-write workloads. Read the tables with that in mind: an immutable-only library is not compared with an
|
||||
editable `json_view` unless it says so.
|
||||
|
||||
The engines do not all offer the same features, which the numbers should be read with:
|
||||
|
||||
| engine | document | random access | editable | notes |
|
||||
|---|---|---|---|---|
|
||||
| `json_view` | immutable index into the text | yes | no | a fresh document per parse; "reused" parses into the same document |
|
||||
| `json_view` (editable) | index into the text plus edits | yes | yes, in place | `json_editable_document`; keeps member order and number spellings |
|
||||
| yyjson | immutable (`yyjson_read`) | yes | via a mutable copy | |
|
||||
| simdjson DOM | immutable, parser reused | yes | no | "fresh" uses a new parser per parse |
|
||||
| simdjson On-Demand | none: forward-only, lazy | no | no | only traverse and select |
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
// Same-feature-set benchmark: read-only JSON documents with random access.
|
||||
//
|
||||
// json_view nlohmann/json_view.hpp (fresh document per parse / reused;
|
||||
// "(editable)": json_editable_document, same read API)
|
||||
// yyjson yyjson_read(): immutable document, random access
|
||||
// json_view nlohmann/json_view.hpp (fresh document per parse / reused)
|
||||
// yyjson yyjson_read(): immutable document, random access
|
||||
// simdjson DOM dom::parser (reused, as recommended; "fresh": a new parser
|
||||
// per parse): immutable, random access
|
||||
// references (different feature sets):
|
||||
@@ -20,11 +19,7 @@
|
||||
//
|
||||
// Workloads: parse (build + free), traverse (visit everything, convert every
|
||||
// number, touch every string and key), select (a few fields per document),
|
||||
// dump (compact serialization of the parsed document), and parse+dump (a fresh
|
||||
// document, then its dump, in one timed call). json_view converts floating-point
|
||||
// numbers lazily (when read or dumped), yyjson and simdjson DOM while parsing:
|
||||
// for float-heavy documents, traverse (which includes the parse) and parse+dump
|
||||
// are the fair reading of the parse and dump rows.
|
||||
// dump (compact serialization of the parsed document).
|
||||
// All engines run interleaved in every round, each timed call after an untimed
|
||||
// one of the same engine; the best round is reported.
|
||||
#include <nlohmann/json_view.hpp>
|
||||
@@ -48,8 +43,6 @@
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_document;
|
||||
using nlohmann::json_editable_document;
|
||||
using nlohmann::json_editable_view;
|
||||
using nlohmann::json_view;
|
||||
|
||||
static volatile double g_sink;
|
||||
@@ -62,8 +55,7 @@ struct stats
|
||||
|
||||
// ---------------- traversal ----------------
|
||||
|
||||
template<class V>
|
||||
static void walk_view(V v, stats& st)
|
||||
static void walk(json_view v, stats& st)
|
||||
{
|
||||
++st.nodes;
|
||||
switch (v.type())
|
||||
@@ -72,42 +64,32 @@ static void walk_view(V v, stats& st)
|
||||
for (auto it = v.begin(); it != v.end(); ++it)
|
||||
{
|
||||
st.str += it.key().size();
|
||||
walk_view<V>(*it, st);
|
||||
walk(*it, st);
|
||||
}
|
||||
break;
|
||||
case json::value_t::array:
|
||||
for (const V e : v)
|
||||
for (const json_view e : v)
|
||||
{
|
||||
walk_view<V>(e, st);
|
||||
walk(e, st);
|
||||
}
|
||||
break;
|
||||
case json::value_t::string:
|
||||
st.str += v.get_string().size();
|
||||
break;
|
||||
case json::value_t::number_integer:
|
||||
st.num += static_cast<double>(v.template get<std::int64_t>());
|
||||
st.num += static_cast<double>(v.get<std::int64_t>());
|
||||
break;
|
||||
case json::value_t::number_unsigned:
|
||||
st.num += static_cast<double>(v.template get<std::uint64_t>());
|
||||
st.num += static_cast<double>(v.get<std::uint64_t>());
|
||||
break;
|
||||
case json::value_t::number_float:
|
||||
st.num += v.template get<double>();
|
||||
st.num += v.get<double>();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void walk(json_view v, stats& st)
|
||||
{
|
||||
walk_view<json_view>(v, st);
|
||||
}
|
||||
|
||||
static void walk(json_editable_view v, stats& st)
|
||||
{
|
||||
walk_view<json_editable_view>(v, st);
|
||||
}
|
||||
|
||||
static void walk(const json& j, stats& st)
|
||||
{
|
||||
++st.nodes;
|
||||
@@ -318,21 +300,20 @@ static void walk(const boost::json::value& v, stats& st)
|
||||
// status (one tweet): id, user.screen_name, retweet_count
|
||||
// rpc: method, params.minuend, id
|
||||
|
||||
template<class V>
|
||||
static double pick_view(const std::string& name, V r)
|
||||
static double pick(const std::string& name, json_view r)
|
||||
{
|
||||
double acc = 0;
|
||||
if (name == "twitter" || name == "status")
|
||||
{
|
||||
auto one = [&](V s)
|
||||
auto one = [&](json_view s)
|
||||
{
|
||||
acc += static_cast<double>(s["id"].template get<std::uint64_t>());
|
||||
acc += static_cast<double>(s["id"].get<std::uint64_t>());
|
||||
acc += static_cast<double>(s["user"]["screen_name"].get_string().size());
|
||||
acc += static_cast<double>(s["retweet_count"].template get<std::int64_t>());
|
||||
acc += static_cast<double>(s["retweet_count"].get<std::int64_t>());
|
||||
};
|
||||
if (name == "twitter")
|
||||
{
|
||||
for (const V s : r["statuses"])
|
||||
for (const json_view s : r["statuses"])
|
||||
{
|
||||
one(s);
|
||||
}
|
||||
@@ -344,20 +325,20 @@ static double pick_view(const std::string& name, V r)
|
||||
}
|
||||
else if (name == "citm_catalog")
|
||||
{
|
||||
for (const V p : r["performances"])
|
||||
for (const json_view p : r["performances"])
|
||||
{
|
||||
acc += static_cast<double>(p["id"].template get<std::uint64_t>() + p["eventId"].template get<std::uint64_t>() + p["seatCategories"].size());
|
||||
acc += static_cast<double>(p["id"].get<std::uint64_t>() + p["eventId"].get<std::uint64_t>() + p["seatCategories"].size());
|
||||
}
|
||||
acc += static_cast<double>(r["events"].size());
|
||||
}
|
||||
else if (name == "canada")
|
||||
{
|
||||
const V g = r["features"][0]["geometry"];
|
||||
const json_view g = r["features"][0]["geometry"];
|
||||
acc += static_cast<double>(r["type"].get_string().size() + g["type"].get_string().size() + g["coordinates"].size());
|
||||
}
|
||||
else if (name == "jeopardy")
|
||||
{
|
||||
for (const V q : r)
|
||||
for (const json_view q : r)
|
||||
{
|
||||
acc += q["round"].get_string() == "Final Jeopardy!" ? 1 : 0;
|
||||
acc += static_cast<double>(q["category"].get_string().size());
|
||||
@@ -366,21 +347,11 @@ static double pick_view(const std::string& name, V r)
|
||||
else if (name == "rpc")
|
||||
{
|
||||
acc += static_cast<double>(r["method"].get_string().size());
|
||||
acc += static_cast<double>(r["params"]["minuend"].template get<std::int64_t>() + r["id"].template get<std::int64_t>());
|
||||
acc += static_cast<double>(r["params"]["minuend"].get<std::int64_t>() + r["id"].get<std::int64_t>());
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, json_view r)
|
||||
{
|
||||
return pick_view<json_view>(name, r);
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, json_editable_view r)
|
||||
{
|
||||
return pick_view<json_editable_view>(name, r);
|
||||
}
|
||||
|
||||
static double pick(const std::string& name, const json& r)
|
||||
{
|
||||
double acc = 0;
|
||||
@@ -663,35 +634,15 @@ int main(int argc, char** argv)
|
||||
walk(json::parse(dc.text), a);
|
||||
auto d = json_document::parse(dc.text);
|
||||
walk(d.root(), b);
|
||||
auto ed = json_editable_document::parse(dc.text);
|
||||
stats e;
|
||||
walk(ed.root(), e);
|
||||
yyjson_doc* y = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||
walk(yyjson_doc_get_root(y), c);
|
||||
simdjson::dom::parser p;
|
||||
walk(p.parse(dc.text).value(), dd);
|
||||
const bool ok = a.nodes == b.nodes && a.nodes == e.nodes && a.str == e.str && std::fabs(a.num - e.num) <= 1e-9 * std::fabs(a.num) && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||
const bool ok = a.nodes == b.nodes && a.nodes == c.nodes && a.nodes == dd.nodes && a.str == b.str && a.str == c.str && a.str == dd.str
|
||||
&& std::fabs(a.num - b.num) <= 1e-9 * std::fabs(a.num) && std::fabs(a.num - c.num) <= 1e-9 * std::fabs(a.num);
|
||||
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pe = pick(dc.name, ed.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pe && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||
const double pa = pick(dc.name, json::parse(dc.text)), pb = pick(dc.name, d.root()), pc = pick(dc.name, yyjson_doc_get_root(y)), pd = pick(dc.name, p.parse(dc.text).value());
|
||||
std::printf("check %-13s traverse %s select %s\n", dc.name.c_str(), ok ? "OK" : "MISMATCH", (pa == pb && pa == pc && pa == pd) ? "OK" : "MISMATCH");
|
||||
yyjson_doc_free(y);
|
||||
|
||||
// the dumps of the engines may format floats differently, so only: every dump is non-empty, and the
|
||||
// dump of json_view (also editable) and of json::parse parse back to the same number of nodes
|
||||
const std::string dj = json::parse(dc.text).dump();
|
||||
const std::string dv = d.root().dump();
|
||||
const std::string de = ed.root().dump();
|
||||
std::size_t yn = 0;
|
||||
yyjson_doc* y2 = yyjson_read(dc.text.data(), dc.text.size(), 0);
|
||||
char* yo = yyjson_write(y2, 0, &yn);
|
||||
const bool dumps_ok = !dj.empty() && !dv.empty() && !de.empty() && yo != nullptr && yn > 0 && !simdjson::to_string(p.parse(dc.text).value()).empty();
|
||||
std::free(yo);
|
||||
yyjson_doc_free(y2);
|
||||
stats sj_nodes, sv_nodes, se_nodes;
|
||||
walk(json::parse(dj), sj_nodes);
|
||||
walk(json::parse(dv), sv_nodes);
|
||||
walk(json::parse(de), se_nodes);
|
||||
std::printf("check %-13s dump %s\n", dc.name.c_str(), (dumps_ok && sj_nodes.nodes == a.nodes && sv_nodes.nodes == a.nodes && se_nodes.nodes == a.nodes) ? "OK" : "MISMATCH");
|
||||
}
|
||||
|
||||
std::FILE* csv = std::fopen("bench_view.csv", "w");
|
||||
@@ -718,7 +669,6 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"parse", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = static_cast<double>(d.node_count()); }},
|
||||
{"json_view (reused)", [&] { reused.read(s); g_sink = static_cast<double>(reused.node_count()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = static_cast<double>(d.root().size()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = static_cast<double>(yyjson_doc_get_val_count(d)); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { auto e = sj.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||
{"simdjson DOM (fresh)", [&] { simdjson::dom::parser p; auto e = p.parse(ps).value_unsafe(); g_sink = e.is_object(); }},
|
||||
@@ -730,7 +680,6 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"traverse", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||
{"json_view (reused)", [&] { reused.read(s); stats st; walk(reused.root(), st); g_sink = st.num; }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); stats st; walk(d.root(), st); g_sink = st.num; }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); stats st; walk(yyjson_doc_get_root(d), st); g_sink = st.num; yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { stats st; walk(sj.parse(ps).value_unsafe(), st); g_sink = st.num; }},
|
||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); stats st; walk_od(d.get_value().value_unsafe(), st); g_sink = st.num; }},
|
||||
@@ -742,31 +691,15 @@ int main(int argc, char** argv)
|
||||
workloads.push_back({"select", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||
{"json_view (reused)", [&] { reused.read(s); g_sink = pick(name, reused.root()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); g_sink = pick(name, d.root()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); g_sink = pick(name, yyjson_doc_get_root(d)); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { g_sink = pick(name, sj.parse(ps).value_unsafe()); }},
|
||||
{"simdjson OD", [&] { auto d = od.iterate(ps).value_unsafe(); g_sink = pick_od(name, d); }},
|
||||
{"json::parse", [&] { json j = json::parse(s); g_sink = pick(name, j); }},
|
||||
}});
|
||||
// pipeline: a fresh document, then a dump of it, in one timed call
|
||||
// (simdjson DOM reuses its parser, as in the parse row); the traverse
|
||||
// workload above is already parse+traverse
|
||||
workloads.push_back({"parse+dump", {
|
||||
{"json_view", [&] { auto d = json_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json_view (editable)", [&] { auto d = json_editable_document::parse(s); std::string o = d.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"yyjson", [&] { yyjson_doc* d = yyjson_read(s.data(), s.size(), 0); std::size_t n = 0; char* o = yyjson_write(d, 0, &n); g_sink = static_cast<double>(n); std::free(o); yyjson_doc_free(d); }},
|
||||
{"simdjson DOM", [&] { std::string o = simdjson::to_string(sj.parse(ps).value_unsafe()); g_sink = static_cast<double>(o.size()); }},
|
||||
#if JSON_VIEW_BENCH_BOOST
|
||||
{"Boost.JSON", [&] { boost::json::monotonic_resource mr; auto v = boost::json::parse(s, &mr); std::string o = boost::json::serialize(v); g_sink = static_cast<double>(o.size()); }},
|
||||
#endif
|
||||
{"json::parse", [&] { json j = json::parse(s); std::string o = j.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
}});
|
||||
{
|
||||
// serialization of an already parsed document
|
||||
static json_document vd;
|
||||
vd.read(s);
|
||||
static json_editable_document ed = json_editable_document::parse(s);
|
||||
ed = json_editable_document::parse(s);
|
||||
static yyjson_doc* yd = nullptr;
|
||||
if (yd)
|
||||
{
|
||||
@@ -780,7 +713,6 @@ int main(int argc, char** argv)
|
||||
jd = json::parse(s);
|
||||
workloads.push_back({"dump", {
|
||||
{"json_view", [&] { std::string o = vd.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json_view (editable)", [&] { std::string o = ed.root().dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
{"yyjson", [&] { std::size_t n = 0; char* o = yyjson_write(yd, 0, &n); g_sink = static_cast<double>(n); std::free(o); }},
|
||||
{"simdjson DOM", [&] { std::string o = simdjson::to_string(se); g_sink = static_cast<double>(o.size()); }},
|
||||
{"json::parse", [&] { std::string o = jd.dump(); g_sink = static_cast<double>(o.size()); }},
|
||||
@@ -808,7 +740,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
const double ref = best[0];
|
||||
std::printf("%-13s %-14s", dc.name.c_str(), wl.first.c_str());
|
||||
std::printf("%-13s %-9s", dc.name.c_str(), wl.first.c_str());
|
||||
for (std::size_t k = 0; k < wl.second.size(); ++k)
|
||||
{
|
||||
const double us = best[k] / 1e3;
|
||||
|
||||
@@ -239,8 +239,6 @@ def main():
|
||||
ap.add_argument('--download', action='store_true', help='use pinned downloads instead of system libraries')
|
||||
ap.add_argument('--no-boost', action='store_true', help='skip Boost.JSON')
|
||||
ap.add_argument('--native', action='store_true', help='compile for this CPU (-march=native / -mcpu=native)')
|
||||
ap.add_argument('--aligned', action='store_true',
|
||||
help='also build and run with all functions (and loops) aligned, to make code layout artifacts visible')
|
||||
ap.add_argument('--rounds', type=int, default=30, help='rounds of bench_view (default: 30)')
|
||||
ap.add_argument('--corpus', nargs='*', default=[], help='more files for bench_corpus')
|
||||
ap.add_argument('--build-dir', default=os.path.join(HERE, 'build'), help='where to build (default: build/ next to this script)')
|
||||
@@ -284,46 +282,32 @@ def main():
|
||||
+ ['-I' + d for d in lib.include])
|
||||
objects.append(obj)
|
||||
|
||||
cxx_version = output(cxx + ['--version'])
|
||||
is_clang = 'clang' in cxx_version.lower()
|
||||
# alignment flags: functions and (gcc) loops on 64-byte boundaries
|
||||
align_flags = ['-mllvm', '-align-all-functions=6'] if is_clang else ['-falign-functions=64', '-falign-loops=64']
|
||||
|
||||
# the builds: the default one and, with --aligned, one with aligned code
|
||||
variants = [('default build', args.build_dir, [])]
|
||||
if args.aligned:
|
||||
variants.append(('aligned build', os.path.join(args.build_dir, 'aligned'), align_flags))
|
||||
binaries = {}
|
||||
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
exe = os.path.join(args.build_dir, bench)
|
||||
run(cxx + flags + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||
binaries[bench] = exe
|
||||
|
||||
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||
corpus = [os.path.join(args.data, f) for f in DEFAULT_CORPUS] + args.corpus
|
||||
# label -> {benchmark: output}
|
||||
all_outputs = {}
|
||||
for label, build_dir, extra in variants:
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
binaries = {}
|
||||
for bench in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
exe = os.path.join(build_dir, bench)
|
||||
run(cxx + flags + extra + include + [os.path.join(HERE, bench + '.cpp')] + objects + link + ['-o', exe])
|
||||
binaries[bench] = exe
|
||||
|
||||
# run: bench_view on its documents, bench_corpus on those and the given files
|
||||
outputs = {}
|
||||
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))],
|
||||
cwd=build_dir, capture_output=True, text=True).stdout
|
||||
for name, text in outputs.items():
|
||||
print(text)
|
||||
all_outputs[label] = outputs
|
||||
outputs = {}
|
||||
outputs['bench_view'] = run([binaries['bench_view'], args.data, str(args.rounds)], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_corpus'] = run([binaries['bench_corpus']] + corpus, cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_edit'] = run([binaries['bench_edit'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
outputs['bench_decode'] = run([binaries['bench_decode'], args.data, str(max(1, args.rounds // 2))], cwd=args.build_dir,
|
||||
capture_output=True, text=True).stdout
|
||||
for name, text in outputs.items():
|
||||
print(text)
|
||||
|
||||
# results with their metadata
|
||||
now = datetime.datetime.now()
|
||||
host = re.sub(r'[^A-Za-z0-9-]+', '-', platform.node().split('.')[0]) or 'host'
|
||||
stem = os.path.join(HERE, 'results', f'{now:%Y-%m-%d}-{host}')
|
||||
os.makedirs(os.path.dirname(stem), exist_ok=True)
|
||||
cxx_version = output(cxx + ['--version'])
|
||||
compiler_version = cxx_version.splitlines()[0] if cxx_version else ' '.join(cxx)
|
||||
meta = [
|
||||
('date', f'{now:%Y-%m-%d %H:%M}'),
|
||||
@@ -332,7 +316,6 @@ def main():
|
||||
('OS', f'{platform.system()} {platform.release()} ({platform.machine()})'),
|
||||
('compiler', compiler_version),
|
||||
('flags', ' '.join(flags)),
|
||||
('aligned results', ('included (flags: ' + ' '.join(align_flags) + ')') if args.aligned else 'not included'),
|
||||
('yyjson', libs['yyjson'].version),
|
||||
('simdjson', libs['simdjson'].version),
|
||||
('Boost.JSON', libs['boost'].version if with_boost else 'skipped (not found)'),
|
||||
@@ -345,20 +328,15 @@ def main():
|
||||
f.write('| | |\n|---|---|\n')
|
||||
for key, value in meta:
|
||||
f.write(f'| {key} | {value} |\n')
|
||||
for label, outputs in all_outputs.items():
|
||||
# (without --aligned there is only the default build, and no extra heading level)
|
||||
prefix = f'{label}: ' if args.aligned else ''
|
||||
for name, text in outputs.items():
|
||||
f.write(f'\n## {prefix}{name}\n\n```\n{text.rstrip()}\n```\n')
|
||||
for name, text in outputs.items():
|
||||
f.write(f'\n## {name}\n\n```\n{text.rstrip()}\n```\n')
|
||||
with open(stem + '.csv', 'w', encoding='utf-8') as out:
|
||||
out.write(''.join(f'# {key}: {value}\n' for key, value in meta))
|
||||
for label, build_dir, _ in variants:
|
||||
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
path = os.path.join(build_dir, name + '.csv')
|
||||
if os.path.isfile(path):
|
||||
with open(path, encoding='utf-8') as f:
|
||||
# (the default build keeps its plain section names)
|
||||
out.write(f'# {name}' + (f' ({label})' if args.aligned else '') + '\n' + f.read())
|
||||
for name in ['bench_view', 'bench_corpus', 'bench_edit', 'bench_decode']:
|
||||
path = os.path.join(args.build_dir, name + '.csv')
|
||||
if os.path.isfile(path):
|
||||
with open(path, encoding='utf-8') as f:
|
||||
out.write(f'# {name}\n' + f.read())
|
||||
print(f'results: {stem}.md, {stem}.csv')
|
||||
|
||||
|
||||
|
||||
@@ -629,22 +629,6 @@ static json make_containers()
|
||||
return j;
|
||||
}
|
||||
|
||||
/// many arrays of one number each: one-element arrays are left to the general
|
||||
/// path (CBOR and MessagePack), which reads a lone number slower than the loop
|
||||
/// for arrays of scalars would
|
||||
static json make_singles()
|
||||
{
|
||||
json data = json::array();
|
||||
for (std::size_t i = 0; i < 100000; ++i)
|
||||
{
|
||||
data.push_back(json::array({i}));
|
||||
}
|
||||
|
||||
json j = json::object();
|
||||
j["data"] = std::move(data);
|
||||
return j;
|
||||
}
|
||||
|
||||
/// one flat array of numbers: the scalar decoding path, which must not move
|
||||
static json make_scalars()
|
||||
{
|
||||
@@ -692,8 +676,6 @@ BENCHMARK_CAPTURE(FromBinaryShape, containers / ubjson_optimized, make_container
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bjdata, make_containers, binary_format::bjdata);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bson, make_containers, binary_format::bson);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, containers / bon8, make_containers, binary_format::bon8);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, singles / cbor, make_singles, binary_format::cbor);
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, singles / msgpack, make_singles, binary_format::msgpack);
|
||||
// BSON names every array element, so a large array measures key generation
|
||||
// rather than scalar decoding and is left out here
|
||||
BENCHMARK_CAPTURE(FromBinaryShape, scalars / cbor, make_scalars, binary_format::cbor);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
#include <algorithm> // any_of
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // function
|
||||
#include <random> // mt19937
|
||||
#include <string> // string
|
||||
@@ -89,27 +88,6 @@ inline bool has_duplicate_keys(const nlohmann::ordered_json_view& v)
|
||||
});
|
||||
}
|
||||
|
||||
// the message of an exception about the value at path (a JSON pointer, "" for
|
||||
// the root) whose source bytes are [start, end): the context basic_json adds
|
||||
// with JSON_DIAGNOSTICS and JSON_DIAGNOSTIC_POSITIONS, if enabled, then what
|
||||
inline std::string message(const std::string& id, const std::string& path, std::size_t start, std::size_t end, const std::string& what)
|
||||
{
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
const std::string positions = "(bytes " + std::to_string(start) + "-" + std::to_string(end) + ") ";
|
||||
#else
|
||||
const std::string positions;
|
||||
static_cast<void>(start);
|
||||
static_cast<void>(end);
|
||||
#endif
|
||||
#if JSON_DIAGNOSTICS
|
||||
const std::string context = path.empty() ? "" : "(" + path + ") " + positions;
|
||||
#else
|
||||
const std::string& context = positions;
|
||||
static_cast<void>(path);
|
||||
#endif
|
||||
return "[json.exception." + id + "] " + context + what;
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// the exception a call throws, or "" if it throws none
|
||||
inline std::string exception_of_call(const std::function<void()>& f)
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <valarray>
|
||||
#if JSON_HAS_RANGES
|
||||
#include <ranges>
|
||||
@@ -792,7 +790,7 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
#if JSON_HAS_RANGES && !defined(__MINGW32__)
|
||||
const std::vector<int> numbers = {1, 2};
|
||||
next_construct_fails = true;
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/) noexcept
|
||||
CHECK_THROWS_AS(nlohmann::to_json(j, numbers | std::views::filter([](int /*unused*/)
|
||||
{
|
||||
return true;
|
||||
})), std::bad_alloc&);
|
||||
@@ -806,391 +804,3 @@ TEST_CASE("a failed allocation leaves the value unchanged")
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
// the no-exceptions CI job has no std::bad_alloc to catch; with iterator
|
||||
// debugging (MSVC STL debug builds) containers allocate inside noexcept
|
||||
// functions, so failing an allocation there terminates the program
|
||||
#if !defined(JSON_NOEXCEPTION) && !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
namespace
|
||||
{
|
||||
// whether every child of j names j as its parent (always true without
|
||||
// JSON_DIAGNOSTICS, where no parents are stored)
|
||||
template<class BasicJsonType>
|
||||
bool children_name_their_parent(const BasicJsonType& j)
|
||||
{
|
||||
#if JSON_DIAGNOSTICS
|
||||
if (j.is_array())
|
||||
{
|
||||
for (const auto& element : *j.m_data.m_value.array)
|
||||
{
|
||||
if (element.m_parent != &j)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (j.is_object())
|
||||
{
|
||||
for (const auto& element : *j.m_data.m_value.object)
|
||||
{
|
||||
if (element.second.m_parent != &j)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
static_cast<void>(j);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class guarantee
|
||||
{
|
||||
basic, // the value stays valid and keeps its type
|
||||
strong // the value is unchanged
|
||||
};
|
||||
|
||||
// Runs op on a fresh value make() with the 0th, 1st, 2nd, ... allocation made
|
||||
// by the library failing in turn, until op completes. Every failed attempt
|
||||
// must have thrown std::bad_alloc and left the value valid, with all parent
|
||||
// pointers intact (copying a value asserts the invariant), and for the strong
|
||||
// guarantee exactly as it was.
|
||||
template<class BasicJsonType>
|
||||
void check_exception_guarantee(guarantee g,
|
||||
const std::function<BasicJsonType()>& make,
|
||||
const std::function<void(BasicJsonType&)>& op)
|
||||
{
|
||||
fail_at_alloc_call = -1;
|
||||
|
||||
bool completed = false;
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t n = 0; !completed; ++n)
|
||||
{
|
||||
CAPTURE(n)
|
||||
REQUIRE(n < 1000);
|
||||
|
||||
BasicJsonType j = make();
|
||||
const std::string before = j.dump();
|
||||
const auto type_before = j.type();
|
||||
|
||||
alloc_call_count = 0;
|
||||
fail_at_alloc_call = static_cast<long>(n);
|
||||
try
|
||||
{
|
||||
op(j);
|
||||
completed = true;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
++failures;
|
||||
}
|
||||
fail_at_alloc_call = -1;
|
||||
|
||||
if (!completed)
|
||||
{
|
||||
CHECK((j.type() == type_before));
|
||||
CHECK(children_name_their_parent(j));
|
||||
const BasicJsonType copy(j); // NOLINT(performance-unnecessary-copy-initialization): the copy is the point, it allocates
|
||||
CHECK(copy.dump() == j.dump());
|
||||
if (g == guarantee::strong)
|
||||
{
|
||||
CHECK(j.dump() == before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the operation allocates, so at least one attempt must have failed
|
||||
CHECK(failures > 0);
|
||||
}
|
||||
|
||||
template<class BasicJsonType>
|
||||
void check_insert_exception_guarantees()
|
||||
{
|
||||
using json_t = BasicJsonType;
|
||||
using make_t = std::function<json_t()>;
|
||||
|
||||
// strings of this length do not fit into the small string buffer, so
|
||||
// copying them allocates
|
||||
const json_t val = std::string(64, 'x');
|
||||
const json_t source = json_t::array({std::string(64, 'p'), std::string(64, 'q'), std::string(64, 'r')});
|
||||
|
||||
for (const bool spare_capacity :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(spare_capacity)
|
||||
|
||||
// [ "aaa...", "bbb...", "ccc..." ], with a spare slot if requested
|
||||
const make_t make = [spare_capacity]()
|
||||
{
|
||||
json_t j = json_t::array({std::string(64, 'a'), std::string(64, 'b'), std::string(64, 'c')});
|
||||
if (spare_capacity)
|
||||
{
|
||||
j.push_back(std::string(64, 'd'));
|
||||
j.erase(3);
|
||||
}
|
||||
return json_t(std::move(j));
|
||||
};
|
||||
|
||||
for (const bool at_end :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
CAPTURE(at_end)
|
||||
|
||||
SECTION("insert(pos, const basic_json&)")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, val);
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, basic_json&&)")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::strong, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, json_t(val));
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, size_type, const basic_json&)")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, 3, val);
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, const_iterator, const_iterator)")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, source.begin(), source.end());
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("insert(pos, initializer_list_t)")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::basic, make, [&](json_t& j)
|
||||
{
|
||||
j.insert(at_end ? j.end() : j.begin() + 1, {val, val});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("insert(const_iterator, const_iterator) for objects")
|
||||
{
|
||||
const json_t members = {{"m", std::string(64, 'm')}, {"n", std::string(64, 'n')}, {"o", std::string(64, 'o')}};
|
||||
|
||||
SECTION("no member exists yet")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
||||
{
|
||||
return json_t({{"a", 1}});
|
||||
}, [&](json_t& j)
|
||||
{
|
||||
j.insert(members.begin(), members.end());
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("some members exist already")
|
||||
{
|
||||
check_exception_guarantee<json_t>(guarantee::basic, []()
|
||||
{
|
||||
return json_t({{"a", 1}, {"n", std::string(64, 'x')}});
|
||||
}, [&](json_t& j)
|
||||
{
|
||||
j.insert(members.begin(), members.end());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("insert() provides its documented exception guarantees (#5986)")
|
||||
{
|
||||
SECTION("std::map-backed object_t")
|
||||
{
|
||||
check_insert_exception_guarantees<nlohmann::json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
|
||||
SECTION("ordered_map-backed object_t")
|
||||
{
|
||||
check_insert_exception_guarantees<nlohmann::ordered_json::with_allocator_t<nth_alloc_fails_allocator>>();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
// the allocation numbered sweep_fail_at (counted from 1) throws; 0 never fails
|
||||
std::size_t sweep_allocations = 0;
|
||||
std::size_t sweep_fail_at = 0;
|
||||
std::ptrdiff_t sweep_live = 0;
|
||||
|
||||
template<class T>
|
||||
struct sweep_allocator
|
||||
{
|
||||
using value_type = T;
|
||||
|
||||
sweep_allocator() = default;
|
||||
template<class U> sweep_allocator(const sweep_allocator<U>& /*unused*/) noexcept {}
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (sweep_fail_at != 0 && ++sweep_allocations == sweep_fail_at)
|
||||
{
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
++sweep_live;
|
||||
return std::allocator<T>().allocate(n);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n) noexcept
|
||||
{
|
||||
--sweep_live;
|
||||
std::allocator<T>().deallocate(p, n);
|
||||
}
|
||||
|
||||
// GCC 4.8's std::map calls these on the allocator directly instead of
|
||||
// going through std::allocator_traits
|
||||
std::size_t max_size() const noexcept
|
||||
{
|
||||
return (std::numeric_limits<std::size_t>::max)() / sizeof(T);
|
||||
}
|
||||
|
||||
template<class U, class... Args>
|
||||
void construct(U* p, Args&& ... args) noexcept(noexcept(::new (static_cast<void*>(p)) U(std::forward<Args>(args)...)))
|
||||
{
|
||||
::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
void destroy(U* p) noexcept
|
||||
{
|
||||
static_cast<void>(p); // unused for trivially destructible types
|
||||
p->~U();
|
||||
}
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = sweep_allocator<U>;
|
||||
};
|
||||
|
||||
bool operator==(const sweep_allocator& /*unused*/) const noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
bool operator!=(const sweep_allocator& /*unused*/) const noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// run make() with the allocation numbered k failing, for k = 1, 2, ...: every
|
||||
// failure must be a std::bad_alloc and must not leave anything behind, and
|
||||
// the first k without a failure must yield the value made without failures
|
||||
template<class BasicJsonType, class Make>
|
||||
void sweep_allocations_of(const Make& make)
|
||||
{
|
||||
sweep_fail_at = 0;
|
||||
const BasicJsonType expected = make();
|
||||
const std::ptrdiff_t live_before = sweep_live;
|
||||
|
||||
std::size_t failures = 0;
|
||||
for (std::size_t k = 1; k < 10000; ++k)
|
||||
{
|
||||
sweep_allocations = 0;
|
||||
sweep_fail_at = k;
|
||||
try
|
||||
{
|
||||
const BasicJsonType result = make();
|
||||
sweep_fail_at = 0;
|
||||
CHECK(result == expected);
|
||||
break;
|
||||
}
|
||||
catch (const std::bad_alloc&)
|
||||
{
|
||||
sweep_fail_at = 0;
|
||||
++failures;
|
||||
}
|
||||
CHECK(sweep_live == live_before);
|
||||
}
|
||||
sweep_fail_at = 0;
|
||||
CHECK(failures > 10);
|
||||
CHECK(sweep_live == live_before);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("parse() survives a failing allocation at every point (#5985)")
|
||||
{
|
||||
// strings beyond the small string buffer, nested objects and arrays
|
||||
const std::string text = R"({"a":"a long string that does not fit into the small string buffer1","b":[1,2,[3,{"k":"another string that is longer than any small string buffer"}]],"c":{"x":1,"y":{"z":[]},"w":"yet another string that does not fit into a small buffer"},"d":1,"b":[[],{}]})";
|
||||
|
||||
using sweep_json = nlohmann::json::with_allocator_t<sweep_allocator>;
|
||||
using sweep_ordered_json = nlohmann::ordered_json::with_allocator_t<sweep_allocator>;
|
||||
|
||||
SECTION("json")
|
||||
{
|
||||
sweep_allocations_of<sweep_json>([&]
|
||||
{
|
||||
return sweep_json::parse(text);
|
||||
});
|
||||
}
|
||||
|
||||
SECTION("ordered_json")
|
||||
{
|
||||
sweep_allocations_of<sweep_ordered_json>([&]
|
||||
{
|
||||
return sweep_ordered_json::parse(text);
|
||||
});
|
||||
}
|
||||
|
||||
// With iterator debugging (MSVC STL debug builds, also used by clang-cl),
|
||||
// containers allocate a debug proxy through the allocator inside their
|
||||
// noexcept move constructors, so failing that allocation terminates the
|
||||
// program instead of throwing std::bad_alloc. Nothing to check there.
|
||||
#if !(defined(_ITERATOR_DEBUG_LEVEL) && _ITERATOR_DEBUG_LEVEL > 0)
|
||||
SECTION("parser callback")
|
||||
{
|
||||
const auto keep_all = [](int /*depth*/, sweep_json::parse_event_t /*event*/, sweep_json& /*parsed*/) noexcept
|
||||
{
|
||||
return true;
|
||||
};
|
||||
sweep_allocations_of<sweep_json>([&]
|
||||
{
|
||||
return sweep_json::parse(text, keep_all);
|
||||
});
|
||||
|
||||
const auto drop_d = [](int /*depth*/, sweep_json::parse_event_t event, sweep_json & parsed)
|
||||
{
|
||||
return event != sweep_json::parse_event_t::key || parsed != "d";
|
||||
};
|
||||
sweep_allocations_of<sweep_json>([&]
|
||||
{
|
||||
return sweep_json::parse(text, drop_d);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("sax_parse into an existing value")
|
||||
{
|
||||
// a failed parse leaves the target in a state that is safe to destroy
|
||||
using sax_t = nlohmann::detail::json_sax_dom_parser<sweep_json, nlohmann::detail::iterator_input_adapter<const char*>>;
|
||||
sweep_allocations_of<sweep_json>([&]() -> sweep_json
|
||||
{
|
||||
sweep_json value;
|
||||
sax_t sax(value, false);
|
||||
static_cast<void>(sweep_json::sax_parse(text, &sax));
|
||||
return value;
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <nlohmann/json_view.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
@@ -374,23 +373,6 @@ TEST_CASE("alternative string type")
|
||||
CHECK_FALSE(j.contains(alt_json::json_pointer("/foo/bar")));
|
||||
}
|
||||
|
||||
SECTION("non-const operator[](json_pointer)")
|
||||
{
|
||||
// creating values must compile without string_t::begin()
|
||||
alt_json j = alt_json::parse(R"({"a":{"b":[1]},"c":null})");
|
||||
const alt_json before = j;
|
||||
j[alt_json::json_pointer("/a/b/3/x/-/y")] = 1; // pad, append, create
|
||||
j[alt_json::json_pointer("/a/z")] = 2; // insert a key
|
||||
j[alt_json::json_pointer("/c/0/q")] = 3; // convert null
|
||||
j[alt_json::json_pointer("/a/b/-")] = 4; // append
|
||||
CHECK(j.dump() == R"({"a":{"b":[1,null,null,{"x":[{"y":1}]},4],"z":2},"c":[{"q":3}]})");
|
||||
|
||||
// a failing access leaves the value unchanged
|
||||
j = before;
|
||||
CHECK_THROWS_AS(j[alt_json::json_pointer("/a/n/99999999999999999999999")], alt_json::out_of_range&);
|
||||
CHECK(j == before);
|
||||
}
|
||||
|
||||
SECTION("operator/(std::size_t)")
|
||||
{
|
||||
// json_pointer::operator/=(std::size_t) must compile without string_t
|
||||
@@ -484,56 +466,4 @@ TEST_CASE("alternative string type")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("alternative string type with json_view")
|
||||
{
|
||||
// regression test for #5994: basic_json_view::dump() called
|
||||
// StringType::capacity(), which alt_string (and the documented StringType
|
||||
// requirements) do not provide
|
||||
using alt_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::read_only>;
|
||||
using alt_editable_document = nlohmann::basic_json_document<alt_json, nlohmann::view_mode::editable>;
|
||||
|
||||
const char* text = R"({"a":[1,"x\u00e4"],"b":{"c":true,"d":null},"e":2.5})";
|
||||
const alt_json j = alt_json::parse(text);
|
||||
|
||||
SECTION("read-only document")
|
||||
{
|
||||
const alt_document d = alt_document::parse(text);
|
||||
CHECK(d.root().dump() == j.dump());
|
||||
CHECK(d.root().dump(2) == j.dump(2));
|
||||
CHECK(d.root().dump(-1, ' ', true) == j.dump(-1, ' ', true));
|
||||
CHECK(d.root().materialize() == j);
|
||||
CHECK(d.root().contains(alt_string("b")));
|
||||
CHECK(d.root()[alt_string("b")][alt_string("c")].dump() == "true");
|
||||
}
|
||||
|
||||
SECTION("long output")
|
||||
{
|
||||
// more than one growth step of the output buffer
|
||||
alt_json big = alt_json::array();
|
||||
for (int i = 0; i < 20000; ++i)
|
||||
{
|
||||
big.push_back(alt_json::parse(text));
|
||||
}
|
||||
const alt_string big_text = big.dump();
|
||||
const alt_document d = alt_document::parse(big_text);
|
||||
CHECK(d.root().dump() == big_text);
|
||||
CHECK(d.root().dump(4) == big.dump(4));
|
||||
}
|
||||
|
||||
SECTION("editable document")
|
||||
{
|
||||
alt_editable_document d = alt_editable_document::parse(text);
|
||||
d.set(d.root(), "f", "new");
|
||||
d.push_back(d.root()[alt_string("a")], 3);
|
||||
alt_json expected = j;
|
||||
expected["f"] = "new";
|
||||
expected["a"].push_back(3);
|
||||
CHECK(d.root().materialize() == expected);
|
||||
CHECK(alt_json::parse(d.root().dump()) == expected);
|
||||
|
||||
const alt_editable_document loaded = alt_editable_document::load(d.save());
|
||||
CHECK(loaded.root().materialize() == expected);
|
||||
}
|
||||
}
|
||||
|
||||
DOCTEST_CLANG_SUPPRESS_WARNING_POP
|
||||
@@ -14,9 +14,7 @@ using nlohmann::json;
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -769,69 +767,3 @@ TEST_CASE("a write that throws leaves the same bytes in every kind of output")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
namespace
|
||||
{
|
||||
|
||||
// an adapter whose every write fails
|
||||
class failing_adapter : public nlohmann::detail::output_adapter_protocol<char>
|
||||
{
|
||||
public:
|
||||
[[noreturn]] void write_character(char /*c*/) override
|
||||
{
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
|
||||
[[noreturn]] void write_characters(const char* /*s*/, std::size_t /*length*/) override
|
||||
{
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
TEST_CASE("output adapters")
|
||||
{
|
||||
SECTION("writing a single character")
|
||||
{
|
||||
// the writers pass whole buffers on, but an adapter takes single
|
||||
// characters as well
|
||||
std::vector<char> as_vector;
|
||||
nlohmann::detail::output_vector_adapter<char> vector_adapter(as_vector);
|
||||
vector_adapter.write_character('a');
|
||||
vector_adapter.write_character('b');
|
||||
CHECK(as_vector == std::vector<char>({'a', 'b'}));
|
||||
|
||||
std::ostringstream as_stream;
|
||||
nlohmann::detail::output_stream_adapter<char> stream_adapter(as_stream);
|
||||
stream_adapter.write_character('c');
|
||||
stream_adapter.write_character('d');
|
||||
CHECK(as_stream.str() == "cd");
|
||||
|
||||
std::string as_string;
|
||||
nlohmann::detail::output_string_adapter<char> string_adapter(as_string);
|
||||
string_adapter.write_character('e');
|
||||
string_adapter.write_character('f');
|
||||
CHECK(as_string == "ef");
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("a sink that is destroyed with characters left over")
|
||||
{
|
||||
// the destructor passes the pending characters on, as a write that
|
||||
// threw leaves them there; the adapter failing then must not escape
|
||||
{
|
||||
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||
sink.write_character('x');
|
||||
}
|
||||
|
||||
// while flush() reports the failure
|
||||
nlohmann::detail::output_adapter_sink<char> sink(std::make_shared<failing_adapter>());
|
||||
sink.write_character('x');
|
||||
CHECK_THROWS_AS(sink.flush(), std::runtime_error&);
|
||||
// (the character counts as written, so there is nothing left for the destructor)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -26,14 +26,11 @@ using nlohmann::ordered_json;
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "test_utils.hpp"
|
||||
#include "sax_countdown.hpp"
|
||||
#include "sax_event_loggers.hpp"
|
||||
using utils::SaxCountdown;
|
||||
using utils::SaxEventLogger;
|
||||
|
||||
|
||||
// trait_test_arg and the "value_in_range_of trait" TEST_CASE_TEMPLATE_DEFINE
|
||||
@@ -2312,23 +2309,6 @@ TEST_CASE("BJData")
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("every event of an ndarray of chars")
|
||||
{
|
||||
// the elements of an ndarray of 'C' are read one by one
|
||||
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c', 'd'};
|
||||
// start_object, key, string, key, start_array, 2 numbers, end_array,
|
||||
// key, start_array, 4 chars, end_array, end_object
|
||||
constexpr int events = 16;
|
||||
for (int allowed = 0; allowed < events; ++allowed)
|
||||
{
|
||||
CAPTURE(allowed)
|
||||
SaxCountdown scp(allowed);
|
||||
CHECK_FALSE(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
SaxCountdown scp(events);
|
||||
CHECK(json::sax_parse(v, &scp, json::input_format_t::bjdata));
|
||||
}
|
||||
|
||||
SECTION("start_array() in ndarray _ArrayData_")
|
||||
{
|
||||
std::vector<uint8_t> const v = {'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 6, 5, 4, 3, 2, 1};
|
||||
@@ -3112,15 +3092,6 @@ TEST_CASE("BJData")
|
||||
json _;
|
||||
CHECK_THROWS_WITH(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82");
|
||||
}
|
||||
|
||||
SECTION("ndarray of chars cut off by the end of the input")
|
||||
{
|
||||
// 2x2 chars announced, three present
|
||||
std::vector<uint8_t> const v = {'[', '$', 'C', '#', '[', '$', 'U', '#', 'U', 2, 2, 2, 'a', 'b', 'c'};
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.110] parse error at byte 16: syntax error while parsing BJData char: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bjdata(v, true, false).is_discarded());
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("byte")
|
||||
@@ -4775,77 +4746,3 @@ TEST_CASE("BJData optimized container type marker is validated for count 0")
|
||||
CHECK(json::from_bjdata(vb) == json::binary({}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData optimized arrays from contiguous and non-contiguous input")
|
||||
{
|
||||
// contiguous input reads the numbers of an array with a type marker in
|
||||
// one loop, other input number by number; both must give the same events,
|
||||
// value, or error for the complete document and for every truncation of
|
||||
// it, and stop at every event a SAX handler rejects
|
||||
json document =
|
||||
{
|
||||
{"u8", {0, 200, 255}},
|
||||
{"i8", {-5, 100, -128}},
|
||||
{"i16", {-30000, 1000, 30000}},
|
||||
{"i32", {-2000000000, 5, 70000}},
|
||||
{"i64", {-9000000000000000000LL, 1, 5000000000LL}},
|
||||
{"f64", {1.5, -0.1, 2.5}},
|
||||
{"pairs", {{1.25, -3.75}, {1.25, -3.75}, json::array()}},
|
||||
{"mixed", {1, "a", 2.5, nullptr}},
|
||||
{"u16", {40000, 1, 65535}},
|
||||
{"u32", {4000000000U, 1, 70000}},
|
||||
{"u64", {18446744073709551615ULL, 1, 5000000000ULL}},
|
||||
{"nd", {{"_ArrayType_", "double"}, {"_ArraySize_", {2, 2}}, {"_ArrayData_", {1.5, -2.5, 3.5, -4.5}}}},
|
||||
{"nd8", {{"_ArrayType_", "uint8"}, {"_ArraySize_", {3}}, {"_ArrayData_", {1, 2, 3}}}},
|
||||
};
|
||||
const std::vector<std::uint8_t> encoded = json::to_bjdata(document, true, true);
|
||||
CHECK(json::from_bjdata(encoded) == document);
|
||||
|
||||
for (std::size_t length = 0; length <= encoded.size(); ++length)
|
||||
{
|
||||
CAPTURE(length)
|
||||
const std::vector<std::uint8_t> contiguous(encoded.begin(), encoded.begin() + static_cast<std::ptrdiff_t>(length));
|
||||
const std::list<std::uint8_t> bytewise(contiguous.begin(), contiguous.end());
|
||||
|
||||
CHECK(json::from_bjdata(contiguous, true, false).dump() == json::from_bjdata(bytewise.begin(), bytewise.end(), true, false).dump());
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
std::string contiguous_error;
|
||||
std::string bytewise_error;
|
||||
try
|
||||
{
|
||||
json _;
|
||||
_ = json::from_bjdata(contiguous);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
contiguous_error = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
json _;
|
||||
_ = json::from_bjdata(bytewise.begin(), bytewise.end());
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
bytewise_error = e.what();
|
||||
}
|
||||
CHECK(contiguous_error == bytewise_error);
|
||||
#endif
|
||||
|
||||
SaxEventLogger from_contiguous;
|
||||
SaxEventLogger from_bytewise;
|
||||
CHECK(json::sax_parse(contiguous, &from_contiguous, json::input_format_t::bjdata)
|
||||
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata));
|
||||
CHECK(from_contiguous.events == from_bytewise.events);
|
||||
}
|
||||
|
||||
for (int events = 0; events <= 80; ++events)
|
||||
{
|
||||
CAPTURE(events)
|
||||
const std::list<std::uint8_t> bytewise(encoded.begin(), encoded.end());
|
||||
SaxCountdown from_contiguous(events);
|
||||
SaxCountdown from_bytewise(events);
|
||||
CHECK(json::sax_parse(encoded, &from_contiguous, json::input_format_t::bjdata)
|
||||
== json::sax_parse(bytewise.begin(), bytewise.end(), &from_bytewise, json::input_format_t::bjdata));
|
||||
}
|
||||
}
|
||||
@@ -329,9 +329,6 @@ TEST_CASE("BON8")
|
||||
check_bon8({false, nullptr, true}, {0x83, 0xF8, 0xFA, 0xF9});
|
||||
check_bon8({false, nullptr, true, 1.0}, {0x84, 0xF8, 0xFA, 0xF9, 0xFD});
|
||||
check_bon8({false, nullptr, true, 1.0, json::array(), 0.0}, {0x85, 0xF8, 0xFA, 0xF9, 0xFD, 0x80, 0xFC, 0xFE});
|
||||
// an array that starts with a number: -1, 0, and 1 have bytes of their own
|
||||
check_bon8({-1.0, 0.0, 1.0}, {0x83, 0xFB, 0xFC, 0xFD});
|
||||
check_bon8({1.0, -1.0, -0.0}, {0x83, 0xFD, 0xFB, 0x8E, 0x80, 0x00, 0x00, 0x00});
|
||||
check_bon8({{{1}}}, {0x81, 0x81, 0x81, 0x91});
|
||||
check_bon8({{{"foo"}}}, {0x81, 0x81, 0x81, 'f', 'o', 'o', 0xFF});
|
||||
check_bon8({{{""}}}, {0x81, 0x81, 0x81, 0xFF});
|
||||
|
||||
+3
-302
@@ -19,11 +19,9 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include "make_test_data_available.hpp"
|
||||
@@ -82,20 +80,8 @@ class huge_string_t : public std::string
|
||||
return result;
|
||||
}
|
||||
|
||||
// returns a copy of @a s whose size() pretends to be @a n
|
||||
static huge_string_t as_size(const std::string& s, const size_type n)
|
||||
{
|
||||
huge_string_t result(s);
|
||||
result.fake_size = n;
|
||||
return result;
|
||||
}
|
||||
|
||||
size_type size() const noexcept
|
||||
{
|
||||
if (fake_size != 0)
|
||||
{
|
||||
return fake_size;
|
||||
}
|
||||
if (pretend_huge)
|
||||
{
|
||||
// one byte more than the BSON length field can represent
|
||||
@@ -106,7 +92,6 @@ class huge_string_t : public std::string
|
||||
|
||||
private:
|
||||
bool pretend_huge = false;
|
||||
size_type fake_size = 0;
|
||||
};
|
||||
|
||||
using huge_string_json = nlohmann::json::with_string_t<huge_string_t>;
|
||||
@@ -281,17 +266,6 @@ TEST_CASE("BSON")
|
||||
CHECK_THROWS_WITH_AS(huge_string_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_string_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("an array element that no longer fits the document")
|
||||
{
|
||||
// the string fits the length field, but not the document that
|
||||
// holds it; nothing of it is written
|
||||
const auto limit = static_cast<std::string::size_type>((std::numeric_limits<std::int32_t>::max)());
|
||||
huge_string_json j;
|
||||
j["a"] = huge_string_json::array({huge_string_t::as_size("value", limit - 8)});
|
||||
|
||||
CHECK_THROWS_AS(huge_string_json::to_bson(j), huge_string_json::out_of_range&);
|
||||
}
|
||||
|
||||
SECTION("document")
|
||||
{
|
||||
// an oversized string nested one level deep makes the
|
||||
@@ -923,38 +897,6 @@ TEST_CASE("BSON")
|
||||
CHECK_THROWS_AS(_ = json::from_bson(input), json::parse_error&);
|
||||
}
|
||||
|
||||
SECTION("binary member with subtype 0x02 (old binary) with a truncated inner length")
|
||||
{
|
||||
// the inner length field is cut off by the end of the input
|
||||
std::vector<std::uint8_t> const input =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x05, 'a', 0x00, // entry: binary
|
||||
0x08, 0x00, 0x00, 0x00, // size of binary (little endian)
|
||||
0x02, // "old binary" subtype
|
||||
0x01 // first byte of the inner length
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("string member that is cut off by the end of the input")
|
||||
{
|
||||
std::vector<std::uint8_t> const input =
|
||||
{
|
||||
0x0F, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x02, 'a', 0x00, // entry: string
|
||||
0x0A, 0x00, 0x00, 0x00, // size of the string (little endian)
|
||||
'b', 'c' // the string ends early
|
||||
};
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.110] parse error at byte 14: syntax error while parsing BSON string: unexpected end of input", json::parse_error&);
|
||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
||||
}
|
||||
|
||||
SECTION("Some more complex document")
|
||||
{
|
||||
json const j =
|
||||
@@ -1222,13 +1164,7 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
// contiguous input reads a key up to its \x00-byte in one step, a stream
|
||||
// reads it byte by byte; both must give the same value or error for the
|
||||
// complete document and for every truncation of it
|
||||
json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||
// keys and array element names ("0" to "11") that end at every position of
|
||||
// an 8-byte word, which contiguous input scans at a time
|
||||
for (std::size_t length = 1; length <= 17; ++length)
|
||||
{
|
||||
j[std::string(length, static_cast<char>('a' + length))] = json::array({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, {11}});
|
||||
}
|
||||
const json j = {{"", true}, {"k", {1, 2, 3}}, {std::string(40, 'x'), {{"nested key", "value"}}}};
|
||||
const std::vector<std::uint8_t> bson = json::to_bson(j);
|
||||
CHECK(json::from_bson(bson) == j);
|
||||
|
||||
@@ -1248,7 +1184,7 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
}
|
||||
try
|
||||
{
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(input.data()), input.size()));
|
||||
std::istringstream stream(std::string(input.begin(), input.end()));
|
||||
from_stream = json::from_bson(stream).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
@@ -1256,72 +1192,8 @@ TEST_CASE("BSON keys from contiguous and stream input")
|
||||
from_stream = e.what();
|
||||
}
|
||||
CHECK(from_vector == from_stream);
|
||||
|
||||
// error_handler_t::strict checks the array element names in place on
|
||||
// contiguous input and byte by byte otherwise; the names are
|
||||
// well-formed, so both give the same value or error as above
|
||||
std::string strict_from_vector;
|
||||
std::string strict_from_list;
|
||||
try
|
||||
{
|
||||
strict_from_vector = json::from_bson(input, true, true, json::error_handler_t::strict).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
strict_from_vector = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
const std::list<std::uint8_t> list(input.begin(), input.end());
|
||||
strict_from_list = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict).dump();
|
||||
}
|
||||
catch (const json::parse_error& e)
|
||||
{
|
||||
strict_from_list = e.what();
|
||||
}
|
||||
CHECK(strict_from_vector == from_vector);
|
||||
CHECK(strict_from_list == from_vector);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BSON array element names")
|
||||
{
|
||||
// an array's element names are not part of the value, so they are not
|
||||
// passed on; error_handler_t::strict still rejects ill-formed UTF-8 in them
|
||||
const std::vector<std::uint8_t> bson =
|
||||
{
|
||||
0x1D, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x04, 'a', 0x00, // entry: array "a"
|
||||
0x15, 0x00, 0x00, 0x00, // size (little endian)
|
||||
0x08, 0xFF, 0x00, 0x01, // entry: boolean named "\xFF", true
|
||||
0x08, '1', '2', '3', '4', '5', '6', '7', '8', '9', 0x00, 0x00, // entry: boolean named "123456789", false
|
||||
0x00, // end of the array
|
||||
0x00 // end of the document
|
||||
};
|
||||
const json expected = {{"a", {true, false}}};
|
||||
|
||||
CHECK(json::from_bson(bson) == expected);
|
||||
CHECK(json::from_bson(bson, true, true, json::error_handler_t::replace) == expected);
|
||||
CHECK(json::from_bson(bson, true, true, json::error_handler_t::ignore) == expected);
|
||||
|
||||
json _;
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(bson, true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
|
||||
std::istringstream stream(std::string(reinterpret_cast<const char*>(bson.data()), bson.size()));
|
||||
CHECK(json::from_bson(stream) == expected);
|
||||
|
||||
// input that is not contiguous reads the names byte by byte and must reject
|
||||
// the same name
|
||||
const std::list<std::uint8_t> list(bson.begin(), bson.end());
|
||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(list.begin(), list.end(), true, true, json::error_handler_t::strict), "[json.exception.parse_error.113] parse error at byte 14: syntax error while parsing BSON key: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||
|
||||
// without exceptions, the rejected name ends the parse
|
||||
CHECK(json::from_bson(bson, true, false, json::error_handler_t::strict).is_discarded());
|
||||
|
||||
// well-formed names pass the check without being stored
|
||||
const json valid = {{"a", {true, false, {{"k", json::array({1, 2})}}}}};
|
||||
CHECK(json::from_bson(json::to_bson(valid), true, true, json::error_handler_t::strict) == valid);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("Negative size of binary value")
|
||||
@@ -1439,9 +1311,7 @@ TEST_CASE("BSON declared document size bounds the content")
|
||||
// records the events that a SAX parser receives
|
||||
struct recorder : nlohmann::json_sax<json>
|
||||
{
|
||||
// GCC's -Weffc++ wants the member initialized in the member initialization list
|
||||
// NOLINTNEXTLINE(readability-redundant-member-init)
|
||||
std::string events {};
|
||||
std::string events;
|
||||
|
||||
bool null() override
|
||||
{
|
||||
@@ -2230,25 +2100,6 @@ value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
||||
CHECK(output.empty());
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
SECTION("U+0000 in a long key is found at any position")
|
||||
{
|
||||
// the key is searched eight characters at a time
|
||||
for (const std::string& key :
|
||||
{
|
||||
std::string("abc\0defghijkl", 12), std::string("abcdefgh\0ijkl", 13), std::string("abcdefghijklmnop\0", 17), std::string("\0abcdefghijkl", 13),
|
||||
std::string("abcdefghijklmno\0", 16)
|
||||
})
|
||||
{
|
||||
CAPTURE(key)
|
||||
const json value = json::object({{key, 1}});
|
||||
std::vector<std::uint8_t> output;
|
||||
CHECK_THROWS_AS(json::to_bson(value, output), json::out_of_range&);
|
||||
CHECK(output.empty());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SECTION("a binary subtype that doesn't fit a byte is rejected before anything is written (#5675)")
|
||||
{
|
||||
// the offending value is nested, so this also covers that the check
|
||||
@@ -2414,153 +2265,3 @@ TEST_CASE("BSON large strings and binaries (chunked reader)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BSON written in one pass and in two passes")
|
||||
{
|
||||
// to_bson() into a returned vector writes each size once the object or
|
||||
// array is complete; through an output adapter, all sizes are computed
|
||||
// first. Both must produce the same bytes and throw the same exceptions.
|
||||
const auto two_pass = [](const json & j, const json::error_handler_t handler)
|
||||
{
|
||||
std::vector<std::uint8_t> result;
|
||||
json::to_bson(j, result, handler);
|
||||
return result;
|
||||
};
|
||||
|
||||
const auto check_same = [&](const json & j, const json::error_handler_t handler)
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', true, json::error_handler_t::replace))
|
||||
CAPTURE(static_cast<int>(handler))
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
std::string one_pass_error;
|
||||
std::string two_pass_error;
|
||||
std::vector<std::uint8_t> one;
|
||||
std::vector<std::uint8_t> two;
|
||||
try
|
||||
{
|
||||
one = json::to_bson(j, handler);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
one_pass_error = e.what();
|
||||
}
|
||||
try
|
||||
{
|
||||
two = two_pass(j, handler);
|
||||
}
|
||||
catch (const json::exception& e)
|
||||
{
|
||||
two_pass_error = e.what();
|
||||
}
|
||||
CHECK(one_pass_error == two_pass_error);
|
||||
CHECK(one == two);
|
||||
#else
|
||||
CHECK(json::to_bson(j, handler) == two_pass(j, handler));
|
||||
#endif
|
||||
};
|
||||
|
||||
const std::array<json::error_handler_t, 4> handlers = {{
|
||||
json::error_handler_t::keep, json::error_handler_t::strict,
|
||||
json::error_handler_t::replace, json::error_handler_t::ignore
|
||||
}
|
||||
};
|
||||
|
||||
SECTION("corpus")
|
||||
{
|
||||
for (const auto& value : utils::round_trip_corpus::values())
|
||||
{
|
||||
const json document = value.is_object() ? value : json{{"value", value}};
|
||||
for (const auto handler : handlers)
|
||||
{
|
||||
check_same(document, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("nesting and arrays of every length class")
|
||||
{
|
||||
json document = json::object();
|
||||
document["empty object"] = json::object();
|
||||
document["empty array"] = json::array();
|
||||
for (const std::size_t n :
|
||||
{
|
||||
1u, 9u, 10u, 11u, 99u, 100u, 101u, 1000u
|
||||
})
|
||||
{
|
||||
json numbers = json::array();
|
||||
json mixed = json::array();
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
numbers.push_back(i % 3 == 0 ? json(static_cast<double>(i) / 3) : json(static_cast<std::int64_t>(i) * 1000003));
|
||||
switch (i % 4)
|
||||
{
|
||||
case 0:
|
||||
mixed.push_back(json("s" + std::to_string(i)));
|
||||
break;
|
||||
case 1:
|
||||
mixed.push_back(json::array({i, json::object({{"k", i}})}));
|
||||
break;
|
||||
case 2:
|
||||
mixed.push_back(json(nullptr));
|
||||
break;
|
||||
default:
|
||||
mixed.push_back(json(i % 2 == 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
document["numbers " + std::to_string(n)] = numbers;
|
||||
document["mixed " + std::to_string(n)] = mixed;
|
||||
document["nested " + std::to_string(n)] = json::object({{"a", numbers}, {"b", json::array({mixed, json::object()})}});
|
||||
}
|
||||
document["binary"] = json::binary({1, 2, 3}, 0x80);
|
||||
document["binary old"] = json::binary({1, 2, 3}, 0x02);
|
||||
for (const auto handler : handlers)
|
||||
{
|
||||
check_same(document, handler);
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
// every entry here throws, which aborts without exceptions
|
||||
SECTION("errors at every position")
|
||||
{
|
||||
// an entry that cannot be written, at each place among valid ones
|
||||
const std::string bad_utf8 = "\xC3\x28";
|
||||
const std::array<json, 7> bad_entries =
|
||||
{
|
||||
{
|
||||
json::object({{std::string("a\0b", 3), 1}}),
|
||||
json::object({{"s", bad_utf8}}),
|
||||
json::object({{bad_utf8, 1}}),
|
||||
json::array({1, 2, bad_utf8}),
|
||||
json::object({{"b", json::binary({1}, 0x100)}}),
|
||||
json::object({{"d", json(json::value_t::discarded)}}),
|
||||
json::array({json(json::value_t::discarded)}),
|
||||
}
|
||||
};
|
||||
for (const auto& bad : bad_entries)
|
||||
{
|
||||
for (std::size_t pos = 0; pos < 4; ++pos)
|
||||
{
|
||||
json document = json::object();
|
||||
json list = json::array();
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
{
|
||||
if (i == pos)
|
||||
{
|
||||
document["x" + std::to_string(i)] = bad;
|
||||
list.push_back(bad);
|
||||
}
|
||||
document["n" + std::to_string(i)] = json::array({i, "str", json::object({{"k", i}})});
|
||||
list.push_back(i);
|
||||
}
|
||||
document["list"] = list;
|
||||
for (const auto handler : handlers)
|
||||
{
|
||||
check_same(document, handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user