mirror of
https://github.com/nlohmann/json.git
synced 2026-07-28 14:43:03 +04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 45c47ef921 | |||
| bb4c522858 | |||
| 95662c8b0c | |||
| 32c2b317af | |||
| 41833047cc | |||
| 0d5b70a95a | |||
| 0c91bbc4ba | |||
| 2aa570f8f3 | |||
| 4b9fc0ad76 | |||
| 41c4589c0a | |||
| 66bf78819f | |||
| 032d90a5ac | |||
| 84939c11be | |||
| 190f4b6a7b | |||
| fb3dd97306 | |||
| baa1cdba04 | |||
| 21be583c79 | |||
| 7f88e8f3a1 |
@@ -15,14 +15,6 @@ guidance.
|
|||||||
|
|
||||||
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
||||||
|
|
||||||
## Unofficial packages
|
|
||||||
|
|
||||||
This project does not publish an official npm package. The npm package
|
|
||||||
[`nlohmann-json`](https://www.npmjs.com/package/nlohmann-json) (or similarly named packages) is not maintained or
|
|
||||||
endorsed by this project. See the
|
|
||||||
[package managers documentation](https://json.nlohmann.me/integration/package_managers/#npm) for supported
|
|
||||||
integration options.
|
|
||||||
|
|
||||||
## Additional Resources
|
## Additional Resources
|
||||||
|
|
||||||
- Explore security-related topics and contribute to tools and projects through
|
- Explore security-related topics and contribute to tools and projects through
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout pull request
|
- name: Checkout pull request
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
path: main
|
path: main
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Checkout tools
|
- name: Checkout tools
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
path: tools
|
path: tools
|
||||||
ref: develop
|
ref: develop
|
||||||
|
|||||||
@@ -32,20 +32,20 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: 'Dependency Review'
|
- name: 'Dependency Review'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -43,6 +43,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
standard: [11, 14, 17, 20, 23, 26]
|
standard: [11, 14, 17, 20, 23, 26]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Install virtual environment
|
- name: Install virtual environment
|
||||||
run: make install_venv -C docs/mkdocs
|
run: make install_venv -C docs/mkdocs
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: "Checkout code"
|
- name: "Checkout code"
|
||||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -76,6 +76,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
# Checkout project source
|
# Checkout project source
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
uses: github/codeql-action/upload-sarif@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: gcc:latest
|
container: gcc:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -43,11 +43,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
|
||||||
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -66,11 +66,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install Valgrind
|
- name: Install Valgrind
|
||||||
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
run: sudo apt-get update ; sudo apt-get install -y valgrind
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -85,11 +85,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
- name: Install git, clang-tools, iwyu (ci_single_binaries), and unzip
|
||||||
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
run: apt-get update ; apt-get install -y git clang-tools iwyu unzip
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -104,11 +104,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install build-essential
|
- name: Install build-essential
|
||||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install dependencies and de_DE locale
|
- name: Install dependencies and de_DE locale
|
||||||
@@ -163,15 +163,7 @@ jobs:
|
|||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
apt-get install -y --no-install-recommends software-properties-common ca-certificates gnupg make git
|
||||||
# add-apt-repository resolves the PPA through the Launchpad API,
|
add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||||
# which intermittently times out or fails the team lookup (the plain
|
|
||||||
# "deb ..." sources below never hit Launchpad and never flake).
|
|
||||||
# Retry with backoff so a transient Launchpad blip does not fail CI.
|
|
||||||
for attempt in 1 2 3 4 5; do
|
|
||||||
add-apt-repository -y ppa:ubuntu-toolchain-r/test && break
|
|
||||||
echo "::warning::add-apt-repository ppa:ubuntu-toolchain-r/test failed (attempt ${attempt}/5); retrying"
|
|
||||||
sleep $((attempt * 10))
|
|
||||||
done
|
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic main"
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ bionic universe"
|
||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial main"
|
||||||
@@ -180,11 +172,11 @@ jobs:
|
|||||||
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
apt-get install -y --no-install-recommends g++-${{ matrix.compiler }}
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -198,11 +190,11 @@ jobs:
|
|||||||
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
|
||||||
container: gcc:${{ matrix.compiler }}
|
container: gcc:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -215,11 +207,11 @@ jobs:
|
|||||||
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', 'latest']
|
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', 'latest']
|
||||||
container: silkeh/clang:${{ matrix.compiler }}
|
container: silkeh/clang:${{ matrix.compiler }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
|
||||||
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
run: echo "JSON_FORCED_GLOBAL_COMPILE_OPTIONS=-DJSON_HAS_FILESYSTEM=0;-DJSON_HAS_EXPERIMENTAL_FILESYSTEM=0" >> "$GITHUB_ENV"
|
||||||
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
if: ${{ matrix.compiler == '7' || matrix.compiler == '8' || matrix.compiler == '9' || matrix.compiler == '10' }}
|
||||||
@@ -235,11 +227,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
standard: [11, 14, 17, 20, 23, 26]
|
standard: [11, 14, 17, 20, 23, 26]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -255,11 +247,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Install git and unzip
|
- name: Install git and unzip
|
||||||
run: apt-get update ; apt-get install -y git unzip
|
run: apt-get update ; apt-get install -y git unzip
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build with libc++
|
- name: Build with libc++
|
||||||
@@ -282,11 +274,11 @@ jobs:
|
|||||||
cuda: ['11.8.0', '12.1.1', '12.6.3']
|
cuda: ['11.8.0', '12.1.1', '12.6.3']
|
||||||
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04
|
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -299,14 +291,14 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ${{ matrix.container }}
|
container: ${{ matrix.container }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# The module test uses `import std;`, which needs CMake's experimental
|
# The module test uses `import std;`, which needs CMake's experimental
|
||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
# Clang: the std library module is provided by libc++ (the image's libstdc++
|
||||||
@@ -328,11 +320,11 @@ jobs:
|
|||||||
# Intel's own last officially published image that still includes it.
|
# Intel's own last officially published image that still includes it.
|
||||||
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -345,9 +337,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: intel/oneapi-hpckit:latest
|
container: intel/oneapi-hpckit:latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -357,9 +349,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DJSON_CI=On
|
run: cmake -S . -B build -DJSON_CI=On
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -375,11 +367,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Install emscripten
|
- name: Install emscripten
|
||||||
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
|
||||||
- name: Build
|
- name: Build
|
||||||
@@ -396,7 +388,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
architecture: [x64, x86]
|
architecture: [x64, x86]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set up MinGW
|
- name: Set up MinGW
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
@@ -86,9 +86,9 @@ jobs:
|
|||||||
runs-on: windows-2025
|
runs-on: windows-2025
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- name: Get latest CMake and ninja
|
- name: Get latest CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
- name: Set extra CXX_FLAGS for latest std_version
|
- name: Set extra CXX_FLAGS for latest std_version
|
||||||
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
|
||||||
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
|
||||||
@@ -122,7 +122,7 @@ jobs:
|
|||||||
runs-on: windows-11-arm
|
runs-on: windows-11-arm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
- name: Run CMake (Release)
|
- name: Run CMake (Release)
|
||||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||||
if: matrix.build_type == 'Release'
|
if: matrix.build_type == 'Release'
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7, 20.1.8]
|
version: [11.0.1, 12.0.1, 13.0.1, 14.0.6, 15.0.7, 16.0.6, 18.1.8, 19.1.7, 20.1.8]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Install Clang
|
- name: Install Clang
|
||||||
@@ -173,7 +173,7 @@ jobs:
|
|||||||
architecture: [Win32, x64]
|
architecture: [Win32, x64]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Run CMake
|
- name: Run CMake
|
||||||
@@ -186,14 +186,14 @@ jobs:
|
|||||||
ci_module_cpp20:
|
ci_module_cpp20:
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
# The module test uses `import std;`, which needs CMake's experimental
|
# The module test uses `import std;`, which needs CMake's experimental
|
||||||
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
# import-std support. Its opt-in token is CMake-version-specific, so pin
|
||||||
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
|
||||||
- name: Get pinned CMake and ninja
|
- name: Get pinned CMake and ninja
|
||||||
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
|
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
|
||||||
with:
|
with:
|
||||||
cmakeVersion: 4.3.4
|
cmakeVersion: 4.3.4
|
||||||
- name: Run CMake (Debug)
|
- name: Run CMake (Debug)
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl
|
|||||||
- [Steve Sperandeo](https://github.com/homer6)
|
- [Steve Sperandeo](https://github.com/homer6)
|
||||||
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
||||||
- [Steve Wagner](https://github.com/ciroque)
|
- [Steve Wagner](https://github.com/ciroque)
|
||||||
|
- [Lion Yang](https://github.com/LionNatsu)
|
||||||
|
|
||||||
### Further support
|
### Further support
|
||||||
|
|
||||||
|
|||||||
@@ -43,17 +43,6 @@ Strong guarantee: if an exception is thrown, there are no changes to any JSON va
|
|||||||
Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON value
|
Throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) if a string stored inside the JSON value
|
||||||
is not UTF-8 encoded and `error_handler` is set to `strict`
|
is not UTF-8 encoded and `error_handler` is set to `strict`
|
||||||
|
|
||||||
!!! warning "Serializing untrusted input"
|
|
||||||
|
|
||||||
When serializing values that may contain invalid or untrusted UTF-8 (e.g., bytes taken directly from network
|
|
||||||
input), `dump()` throws [`type_error.316`](../../home/exceptions.md#jsonexceptiontype_error316) in the default
|
|
||||||
`strict` mode. To serialize such data without throwing, pass
|
|
||||||
[`error_handler_t::replace`](error_handler_t.md) (substitutes U+FFFD) or
|
|
||||||
[`error_handler_t::ignore`](error_handler_t.md). Callers that serialize untrusted input on a crash-sensitive path
|
|
||||||
should either choose a non-strict error handler or wrap `dump()` in a `#!cpp try`/`#!cpp catch`.
|
|
||||||
|
|
||||||
See the [FAQ](../../home/faq.md#serializing-untrusted-or-invalid-utf-8) for details.
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Linear.
|
Linear.
|
||||||
|
|||||||
@@ -40,9 +40,6 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
|
||||||
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
|
||||||
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
|
||||||
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
|
|
||||||
string, or binary value exceeds the range of the 32-bit BSON length field; example:
|
|
||||||
`"BSON length 2147483661 exceeds maximum of 2147483647"`
|
|
||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||||
|
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||||
|
|
||||||
## Library version
|
## Library version
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# JSON_USE_SIMDUTF
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_USE_SIMDUTF
|
||||||
|
```
|
||||||
|
|
||||||
|
When defined, the parser validates the UTF-8 content of JSON strings that come from a **contiguous byte input**
|
||||||
|
(`std::string`, `std::vector<char>`/`<std::uint8_t>`, string literals, `const char*` ranges, …) using the
|
||||||
|
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. On text with many
|
||||||
|
non-ASCII characters (e.g. CJK or emoji) this can validate several times faster.
|
||||||
|
|
||||||
|
This is an **opt-in external dependency**. The library itself remains header-only and its behavior is unchanged: the
|
||||||
|
same input is accepted or rejected either way, and every parse error is reported at the same position with the same
|
||||||
|
message (simdutf is only used to fast-path *valid* runs; anything it flags falls back to the scalar path so the exact
|
||||||
|
diagnostic is preserved). Streaming inputs (files, `std::istream`, wide strings, user-defined adapters) always use the
|
||||||
|
scalar path.
|
||||||
|
|
||||||
|
When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the
|
||||||
|
simdutf library. When it is not defined, no simdutf header is included and there is no dependency.
|
||||||
|
|
||||||
|
## Default definition
|
||||||
|
|
||||||
|
By default, `#!cpp JSON_USE_SIMDUTF` is not defined and the portable C++11 scalar validator is used.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#undef JSON_USE_SIMDUTF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
??? example
|
||||||
|
|
||||||
|
The code below enables the simdutf backend for UTF-8 validation.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define JSON_USE_SIMDUTF 1
|
||||||
|
#include <simdutf.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
The project must also link against simdutf, e.g. with CMake:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_compile_definitions(your_target PRIVATE JSON_USE_SIMDUTF)
|
||||||
|
target_link_libraries(your_target PRIVATE simdutf::simdutf)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version history
|
||||||
|
|
||||||
|
- Added in version 3.12.1.
|
||||||
@@ -898,22 +898,6 @@ A JSON Patch `add` operation cannot be applied because the target location's par
|
|||||||
|
|
||||||
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.
|
||||||
|
|
||||||
### json.exception.out_of_range.412
|
|
||||||
|
|
||||||
BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.
|
|
||||||
|
|
||||||
!!! failure "Example message"
|
|
||||||
|
|
||||||
```
|
|
||||||
BSON length 2147483661 exceeds maximum of 2147483647
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note
|
|
||||||
|
|
||||||
This exception was added in version 3.13.0. Before that, the length was silently truncated, and
|
|
||||||
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
|
|
||||||
[`from_bson`](../api/basic_json/from_bson.md) rejected.
|
|
||||||
|
|
||||||
## Further exceptions
|
## Further exceptions
|
||||||
|
|
||||||
This exception is thrown in case of errors that cannot be classified with the
|
This exception is thrown in case of errors that cannot be classified with the
|
||||||
|
|||||||
@@ -194,27 +194,6 @@ The library uses `std::numeric_limits<number_float_t>::digits10` (15 for IEEE `d
|
|||||||
|
|
||||||
See [this section](../features/types/number_handling.md#number-serialization) on the library's number handling for more information.
|
See [this section](../features/types/number_handling.md#number-serialization) on the library's number handling for more information.
|
||||||
|
|
||||||
### Serializing untrusted or invalid UTF-8
|
|
||||||
|
|
||||||
!!! question "Questions"
|
|
||||||
|
|
||||||
- Why does `dump()` throw when I serialize data that came from the network?
|
|
||||||
- Is CVE-2024-34363 a vulnerability in this library?
|
|
||||||
|
|
||||||
Crashes reported against this library that stem from an uncaught
|
|
||||||
[`type_error.316`](exceptions.md#jsonexceptiontype_error316) while serializing unvalidated input (e.g.,
|
|
||||||
CVE-2024-34363) are a usage issue, not a library vulnerability:
|
|
||||||
[`dump()`](../api/basic_json/dump.md) throws in its default `strict` mode because
|
|
||||||
[RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259#section-8.1) requires JSON text to be valid UTF-8.
|
|
||||||
|
|
||||||
The recommended pattern is to pass a non-strict [`error_handler`](../api/basic_json/error_handler_t.md) or to handle the
|
|
||||||
exception:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// replace invalid sequences with U+FFFD instead of throwing
|
|
||||||
const auto s = j.dump(-1, ' ', false, json::error_handler_t::replace);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using JSON values with `std::format` or `fmt`
|
### Using JSON values with `std::format` or `fmt`
|
||||||
|
|
||||||
!!! question
|
!!! question
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl
|
|||||||
- [Steve Sperandeo](https://github.com/homer6)
|
- [Steve Sperandeo](https://github.com/homer6)
|
||||||
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
|
||||||
- [Steve Wagner](https://github.com/ciroque)
|
- [Steve Wagner](https://github.com/ciroque)
|
||||||
|
- [Lion Yang](https://github.com/LionNatsu)
|
||||||
|
|
||||||
Thanks everyone!
|
Thanks everyone!
|
||||||
|
|||||||
@@ -930,12 +930,6 @@ If you are using [CocoaPods](https://cocoapods.org), you can use the library by
|
|||||||
to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues
|
to your podfile (see [an example](https://bitbucket.org/benman/nlohmann_json-cocoapod/src/master/)). Please file issues
|
||||||
[here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
|
[here](https://bitbucket.org/benman/nlohmann_json-cocoapod/issues?status=new&status=open).
|
||||||
|
|
||||||
## npm
|
|
||||||
|
|
||||||
This project does not publish an official [npm](https://www.npmjs.com) package. The npm package
|
|
||||||
[`nlohmann-json`](https://www.npmjs.com/package/nlohmann-json) (or similarly named packages) is not maintained or
|
|
||||||
endorsed by this project. Use one of the package managers listed above, or integrate the single header directly.
|
|
||||||
|
|
||||||
## ESP-IDF and PlatformIO
|
## ESP-IDF and PlatformIO
|
||||||
|
|
||||||
There is no official package published to the [ESP-IDF Component Registry](https://components.espressif.com) or the
|
There is no official package published to the [ESP-IDF Component Registry](https://components.espressif.com) or the
|
||||||
|
|||||||
@@ -296,6 +296,7 @@ nav:
|
|||||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||||
|
- 'JSON_USE_SIMDUTF': api/macros/json_use_simdutf.md
|
||||||
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
- 'NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_derived_type.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_INTRUSIVE, NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_intrusive.md
|
||||||
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
- 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT, NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE': api/macros/nlohmann_define_type_non_intrusive.md
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ wheel==0.47.0
|
|||||||
|
|
||||||
mkdocs==1.6.1 # documentation framework
|
mkdocs==1.6.1 # documentation framework
|
||||||
mkdocs-git-revision-date-localized-plugin==1.5.3 # plugin "git-revision-date-localized"
|
mkdocs-git-revision-date-localized-plugin==1.5.3 # plugin "git-revision-date-localized"
|
||||||
mkdocs-material==9.7.7 # theme for mkdocs
|
mkdocs-material==9.7.6 # theme for mkdocs
|
||||||
mkdocs-material-extensions==1.3.1 # extensions
|
mkdocs-material-extensions==1.3.1 # extensions
|
||||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||||
mkdocs-redirects==1.2.3 # plugin "redirects"
|
mkdocs-redirects==1.2.3 # plugin "redirects"
|
||||||
|
|||||||
@@ -163,39 +163,12 @@ class binary_reader
|
|||||||
// BSON //
|
// BSON //
|
||||||
//////////
|
//////////
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Validate a BSON document's declared size against the bytes read.
|
|
||||||
|
|
||||||
A BSON document starts with an int32 that counts its own total length in
|
|
||||||
bytes, including that prefix and the trailing 0x00. The reader is driven
|
|
||||||
by the terminator rather than the declared length, so without this check a
|
|
||||||
nested document could declare a length that disagrees with where its
|
|
||||||
terminator actually falls and quietly hand the bytes in between to the
|
|
||||||
enclosing document. A well-formed document is at least 5 bytes (the prefix
|
|
||||||
plus the terminator); the equality also rejects those impossible sizes,
|
|
||||||
since at least 5 bytes are always consumed.
|
|
||||||
|
|
||||||
@param[in] document_start value of chars_read before the size prefix
|
|
||||||
@param[in] document_size the declared document size
|
|
||||||
@return whether the declared size matches the number of bytes read
|
|
||||||
*/
|
|
||||||
bool check_bson_document_size(const std::size_t document_start, const std::int32_t document_size)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(document_size < 0 || static_cast<std::size_t>(document_size) != chars_read - document_start))
|
|
||||||
{
|
|
||||||
return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,
|
|
||||||
exception_message(input_format_t::bson, concat("document size ", std::to_string(document_size), " does not match the number of bytes read (", std::to_string(chars_read - document_start), ")"), "document"), nullptr));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Reads in a BSON-object and passes it to the SAX-parser.
|
@brief Reads in a BSON-object and passes it to the SAX-parser.
|
||||||
@return whether a valid BSON-value was passed to the SAX parser
|
@return whether a valid BSON-value was passed to the SAX parser
|
||||||
*/
|
*/
|
||||||
bool parse_bson_internal()
|
bool parse_bson_internal()
|
||||||
{
|
{
|
||||||
const std::size_t document_start = chars_read;
|
|
||||||
std::int32_t document_size{};
|
std::int32_t document_size{};
|
||||||
get_number<std::int32_t, true>(input_format_t::bson, document_size);
|
get_number<std::int32_t, true>(input_format_t::bson, document_size);
|
||||||
|
|
||||||
@@ -209,11 +182,6 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(document_start, document_size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sax->end_object();
|
return sax->end_object();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +397,6 @@ class binary_reader
|
|||||||
*/
|
*/
|
||||||
bool parse_bson_array()
|
bool parse_bson_array()
|
||||||
{
|
{
|
||||||
const std::size_t document_start = chars_read;
|
|
||||||
std::int32_t document_size{};
|
std::int32_t document_size{};
|
||||||
get_number<std::int32_t, true>(input_format_t::bson, document_size);
|
get_number<std::int32_t, true>(input_format_t::bson, document_size);
|
||||||
|
|
||||||
@@ -443,11 +410,6 @@ class binary_reader
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(document_start, document_size)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sax->end_array();
|
return sax->end_array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1884,29 +1846,6 @@ class binary_reader
|
|||||||
return get_ubjson_value(get_char ? get_ignore_noop() : current);
|
return get_ubjson_value(get_char ? get_ignore_noop() : current);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief reject a negative UBJSON/BJData string length
|
|
||||||
|
|
||||||
String and key lengths are written with signed integer markers (i, I, l,
|
|
||||||
L). A negative value is malformed; without this check get_string() would
|
|
||||||
silently treat it as an empty string and leave the following bytes to be
|
|
||||||
misread as the next value. This mirrors the non-negative check the
|
|
||||||
optimized-container count path already performs in get_ubjson_size_value.
|
|
||||||
|
|
||||||
@param[in] len the string length read from the input
|
|
||||||
@return whether the length is valid (non-negative)
|
|
||||||
*/
|
|
||||||
template<typename NumberType>
|
|
||||||
bool check_ubjson_string_length(const NumberType len)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(len < 0))
|
|
||||||
{
|
|
||||||
return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,
|
|
||||||
exception_message(input_format, "string length must not be negative", "string"), nullptr));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief reads a UBJSON string
|
@brief reads a UBJSON string
|
||||||
|
|
||||||
@@ -1944,25 +1883,25 @@ class binary_reader
|
|||||||
case 'i':
|
case 'i':
|
||||||
{
|
{
|
||||||
std::int8_t len{};
|
std::int8_t len{};
|
||||||
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result);
|
return get_number(input_format, len) && get_string(input_format, len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
{
|
{
|
||||||
std::int16_t len{};
|
std::int16_t len{};
|
||||||
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result);
|
return get_number(input_format, len) && get_string(input_format, len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'l':
|
case 'l':
|
||||||
{
|
{
|
||||||
std::int32_t len{};
|
std::int32_t len{};
|
||||||
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result);
|
return get_number(input_format, len) && get_string(input_format, len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
{
|
{
|
||||||
std::int64_t len{};
|
std::int64_t len{};
|
||||||
return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result);
|
return get_number(input_format, len) && get_string(input_format, len, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'u':
|
case 'u':
|
||||||
|
|||||||
@@ -231,6 +231,33 @@ class iterator_input_adapter
|
|||||||
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
|
std::is_same<IteratorType, SentinelType>::value && std::is_pointer<IteratorType>::value;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Whether the remaining input is a single contiguous block of 1-byte
|
||||||
|
// elements that the lexer can inspect directly (used for the SWAR string
|
||||||
|
// fast path). Restricted to same-type iterator/sentinel pairs so that plain
|
||||||
|
// std::distance/std::advance are well-defined in all standards.
|
||||||
|
static constexpr bool supports_bulk_scan =
|
||||||
|
iterator_is_contiguous && std::is_same<IteratorType, SentinelType>::value && sizeof(char_type) == 1;
|
||||||
|
|
||||||
|
// Pointer to the next unread element; only valid when bulk_remaining() > 0.
|
||||||
|
const char_type* bulk_data() const
|
||||||
|
{
|
||||||
|
return &*current;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number of unread elements available as one contiguous block.
|
||||||
|
std::size_t bulk_remaining() const
|
||||||
|
{
|
||||||
|
return static_cast<std::size_t>(std::distance(current, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume @a n elements previously inspected via bulk_data().
|
||||||
|
void bulk_skip(std::size_t n)
|
||||||
|
{
|
||||||
|
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
// contiguous fast path: bulk copy the remaining range with std::memcpy
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
|
||||||
@@ -566,6 +593,24 @@ typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_typ
|
|||||||
return factory_type::create(first, last);
|
return factory_type::create(first, last);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect a container that stores its elements contiguously as single bytes
|
||||||
|
// (std::string, std::vector<char/unsigned char>, std::array<char, N>,
|
||||||
|
// std::string_view, ...). Such inputs are wrapped in a pointer-based adapter so
|
||||||
|
// they benefit from the contiguous fast paths (bulk string scanning, memcpy for
|
||||||
|
// binary formats) in every C++ standard - not only in C++20, where the standard
|
||||||
|
// library iterators model std::contiguous_iterator and are detected directly.
|
||||||
|
template<typename ContainerType, typename = void>
|
||||||
|
struct is_contiguous_byte_container : std::false_type {};
|
||||||
|
|
||||||
|
template<typename ContainerType>
|
||||||
|
struct is_contiguous_byte_container < ContainerType, void_t <
|
||||||
|
decltype(std::declval<const ContainerType&>().data()),
|
||||||
|
decltype(std::declval<const ContainerType&>().size()) >>
|
||||||
|
: std::integral_constant < bool,
|
||||||
|
std::is_pointer<decltype(std::declval<const ContainerType&>().data())>::value&&
|
||||||
|
std::is_integral<typename std::remove_pointer<decltype(std::declval<const ContainerType&>().data())>::type>::value&&
|
||||||
|
sizeof(typename std::remove_pointer<decltype(std::declval<const ContainerType&>().data())>::type) == 1 > {};
|
||||||
|
|
||||||
// Convenience shorthand from container to iterator
|
// Convenience shorthand from container to iterator
|
||||||
// Enables ADL on begin(container) and end(container)
|
// Enables ADL on begin(container) and end(container)
|
||||||
// Encloses the using declarations in namespace for not to leak them to outside scope
|
// Encloses the using declarations in namespace for not to leak them to outside scope
|
||||||
@@ -593,12 +638,32 @@ struct container_input_adapter_factory< ContainerType,
|
|||||||
|
|
||||||
} // namespace container_input_adapter_factory_impl
|
} // namespace container_input_adapter_factory_impl
|
||||||
|
|
||||||
template<typename ContainerType>
|
// General container path (iterator-based). Contiguous single-byte containers
|
||||||
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(ContainerType&& container)
|
// are excluded here and routed through the pointer-based overload below.
|
||||||
|
template < typename ContainerType,
|
||||||
|
enable_if_t < !is_contiguous_byte_container<ContainerType>::value, int > = 0 >
|
||||||
|
typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(ContainerType && container)
|
||||||
{
|
{
|
||||||
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
|
return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(std::forward<ContainerType>(container));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contiguous single-byte containers (std::string, std::vector<char>, ...) are
|
||||||
|
// wrapped in a pointer-based adapter so the contiguous fast paths apply in every
|
||||||
|
// standard. The pointer keeps the container's own element type (const char* for
|
||||||
|
// std::string, const std::uint8_t* for std::vector<std::uint8_t>, ...), so the
|
||||||
|
// resulting char_type - and therefore the parsing behavior - is byte-for-byte
|
||||||
|
// identical to the iterator-based path; only the raw pointer additionally
|
||||||
|
// enables the bulk fast paths. The container outlives the adapter for the whole
|
||||||
|
// parse (temporaries live until the end of the full expression), exactly as the
|
||||||
|
// iterators it replaces did.
|
||||||
|
template < typename ContainerType,
|
||||||
|
enable_if_t < is_contiguous_byte_container<ContainerType>::value, int > = 0 >
|
||||||
|
auto input_adapter(const ContainerType& container)
|
||||||
|
-> decltype(input_adapter(container.data(), container.data() + container.size()))
|
||||||
|
{
|
||||||
|
return input_adapter(container.data(), container.data() + container.size());
|
||||||
|
}
|
||||||
|
|
||||||
// specialization for std::string
|
// specialization for std::string
|
||||||
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
|
using string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,9 @@
|
|||||||
#include <vector> // vector
|
#include <vector> // vector
|
||||||
|
|
||||||
#include <nlohmann/detail/input/input_adapters.hpp>
|
#include <nlohmann/detail/input/input_adapters.hpp>
|
||||||
|
#include <nlohmann/detail/input/number_parse.hpp>
|
||||||
#include <nlohmann/detail/input/position_t.hpp>
|
#include <nlohmann/detail/input/position_t.hpp>
|
||||||
|
#include <nlohmann/detail/input/string_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
|
|
||||||
@@ -125,6 +127,25 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect whether an input adapter exposes a contiguous byte block that the
|
||||||
|
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
||||||
|
// Adapters without the flag - file, stream, wide-string, user-defined - fall
|
||||||
|
// back to the character-at-a-time string scanner.
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
using detect_supports_bulk_scan = decltype(InputAdapterType::supports_bulk_scan);
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_bulk_scan(std::true_type /*detected*/)
|
||||||
|
{
|
||||||
|
return InputAdapterType::supports_bulk_scan;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename InputAdapterType>
|
||||||
|
constexpr bool input_adapter_supports_bulk_scan(std::false_type /*detected*/)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief lexical analysis
|
@brief lexical analysis
|
||||||
|
|
||||||
@@ -146,6 +167,14 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
static constexpr bool lazy_token_string =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||||
|
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||||
|
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||||
|
/// the per-character capture in get() cannot lose error diagnostics.
|
||||||
|
static constexpr bool bulk_scan =
|
||||||
|
lazy_token_string
|
||||||
|
&& input_adapter_supports_bulk_scan<InputAdapterType>(is_detected<detect_supports_bulk_scan, InputAdapterType> {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
using token_type = typename lexer_base<BasicJsonType>::token_type;
|
||||||
|
|
||||||
@@ -265,6 +294,40 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// contiguous input: bulk-append the run of ordinary characters and complete
|
||||||
|
/// well-formed UTF-8 sequences starting at the current read position, leaving
|
||||||
|
/// the first byte that needs individual handling (the closing quote, an
|
||||||
|
/// escape, a control character, or an ill-formed UTF-8 byte) for get()
|
||||||
|
void scan_string_bulk(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
// a pending unget must be consumed through the normal path first
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::size_t remaining = ia.bulk_remaining();
|
||||||
|
if (remaining == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(ia.bulk_data());
|
||||||
|
|
||||||
|
const std::size_t pos = string_bulk_run(data, remaining);
|
||||||
|
if (pos == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), pos);
|
||||||
|
ia.bulk_skip(pos);
|
||||||
|
// the run contains no newline (all bytes < 0x20 are treated as special),
|
||||||
|
// so only the flat character counters advance
|
||||||
|
position.chars_read_total += pos;
|
||||||
|
position.chars_read_current_line += pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: no bulk fast path
|
||||||
|
void scan_string_bulk(std::false_type /*bulk*/) const noexcept {}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief scan a string literal
|
@brief scan a string literal
|
||||||
|
|
||||||
@@ -290,6 +353,10 @@ class lexer : public lexer_base<BasicJsonType>
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
// bulk-consume ordinary characters from contiguous input, then
|
||||||
|
// handle the next special byte through the switch below
|
||||||
|
scan_string_bulk(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
|
||||||
// get the next character
|
// get the next character
|
||||||
switch (get())
|
switch (get())
|
||||||
{
|
{
|
||||||
@@ -1279,45 +1346,56 @@ scan_number_done:
|
|||||||
// we are done scanning a number)
|
// we are done scanning a number)
|
||||||
unget();
|
unget();
|
||||||
|
|
||||||
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
return convert_number(number_type);
|
||||||
errno = 0;
|
}
|
||||||
|
|
||||||
// try to parse integers first and fall back to floats
|
/*!
|
||||||
|
@brief convert the number text in token_buffer to its value and token type
|
||||||
|
|
||||||
|
The digit sequence in token_buffer has already been validated (by the
|
||||||
|
scan_number() state machine or by the contiguous fast path) and holds the
|
||||||
|
locale decimal point in place of '.'. Integers are parsed first and fall
|
||||||
|
back to floating point on overflow. This is shared so both scanners produce
|
||||||
|
identical results.
|
||||||
|
*/
|
||||||
|
token_type convert_number(token_type number_type)
|
||||||
|
{
|
||||||
|
const char* const num_begin = token_buffer.data();
|
||||||
|
const char* const num_end = num_begin + token_buffer.size();
|
||||||
|
|
||||||
|
// try to parse integers first and fall back to floats; the digit
|
||||||
|
// sequence has already been validated, so a dedicated parser can avoid
|
||||||
|
// the locale/errno overhead of strtoull
|
||||||
if (number_type == token_type::value_unsigned)
|
if (number_type == token_type::value_unsigned)
|
||||||
{
|
{
|
||||||
const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
|
if (parse_integer_unsigned(num_begin, num_end, value_unsigned))
|
||||||
|
|
||||||
// we checked the number format before
|
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
|
||||||
|
|
||||||
if (errno != ERANGE)
|
|
||||||
{
|
|
||||||
value_unsigned = static_cast<number_unsigned_t>(x);
|
|
||||||
if (value_unsigned == x)
|
|
||||||
{
|
{
|
||||||
return token_type::value_unsigned;
|
return token_type::value_unsigned;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (number_type == token_type::value_integer)
|
else if (number_type == token_type::value_integer)
|
||||||
{
|
{
|
||||||
const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
|
if (parse_integer_signed(num_begin, num_end, value_integer))
|
||||||
|
|
||||||
// we checked the number format before
|
|
||||||
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
|
|
||||||
|
|
||||||
if (errno != ERANGE)
|
|
||||||
{
|
|
||||||
value_integer = static_cast<number_integer_t>(x);
|
|
||||||
if (value_integer == x)
|
|
||||||
{
|
{
|
||||||
return token_type::value_integer;
|
return token_type::value_integer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// this code is reached if we parse a floating-point number or if an
|
// this code is reached if we parse a floating-point number or if an
|
||||||
// integer conversion above failed
|
// integer conversion above overflowed. Prefer std::from_chars
|
||||||
|
// (Eisel-Lemire, locale-independent, correctly rounded) when available;
|
||||||
|
// otherwise the exact Clinger fast path (double only); otherwise the
|
||||||
|
// locale-aware strtof/strtod.
|
||||||
|
if (parse_float_from_chars(num_begin, num_end, value_float))
|
||||||
|
{
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
if (parse_float_fast(num_begin, num_end, decimal_point_char, value_float))
|
||||||
|
{
|
||||||
|
return token_type::value_float;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
|
||||||
strtof(value_float, token_buffer.data(), &endptr);
|
strtof(value_float, token_buffer.data(), &endptr);
|
||||||
|
|
||||||
// we checked the number format before
|
// we checked the number format before
|
||||||
@@ -1326,6 +1404,130 @@ scan_number_done:
|
|||||||
return token_type::value_float;
|
return token_type::value_float;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief contiguous fast path for scanning a number
|
||||||
|
|
||||||
|
Parses the whole number token straight from the input buffer, avoiding the
|
||||||
|
per-character get()/add() of scan_number(). On success it fills token_buffer
|
||||||
|
(with the locale decimal point substituted, as scan_number() does) and
|
||||||
|
returns the token type. On anything it does not fully recognize as a
|
||||||
|
well-formed number it makes no state change and returns
|
||||||
|
token_type::uninitialized, so the caller falls back to scan_number(), which
|
||||||
|
then produces the exact diagnostic. @a current is the first digit or the
|
||||||
|
leading minus (already read); the remaining bytes are taken from the adapter.
|
||||||
|
*/
|
||||||
|
token_type scan_number_bulk_contiguous()
|
||||||
|
{
|
||||||
|
// a pending unget offsets the buffer position from current; fall back
|
||||||
|
if (next_unget)
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
const std::size_t rem = ia.bulk_remaining();
|
||||||
|
if (rem == 0)
|
||||||
|
{
|
||||||
|
// the first digit is the last input byte; let scan_number() finish
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
// the byte before the next unread one is current (contiguous input)
|
||||||
|
const char* const data = reinterpret_cast<const char*>(ia.bulk_data()) - 1;
|
||||||
|
const std::size_t avail = rem + 1;
|
||||||
|
|
||||||
|
// validate + classify the number extent (mirrors scan_number()'s grammar)
|
||||||
|
std::size_t i = 0;
|
||||||
|
std::size_t dot_index = std::string::npos;
|
||||||
|
token_type number_type = token_type::value_unsigned;
|
||||||
|
if (data[0] == '-')
|
||||||
|
{
|
||||||
|
number_type = token_type::value_integer;
|
||||||
|
i = 1;
|
||||||
|
if (i >= avail)
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data[i] == '0')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else if (data[i] >= '1' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
if (i < avail && data[i] == '.')
|
||||||
|
{
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
dot_index = i;
|
||||||
|
++i;
|
||||||
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i < avail && (data[i] == 'e' || data[i] == 'E'))
|
||||||
|
{
|
||||||
|
number_type = token_type::value_float;
|
||||||
|
++i;
|
||||||
|
if (i < avail && (data[i] == '+' || data[i] == '-'))
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
if (i >= avail || !(data[i] >= '0' && data[i] <= '9'))
|
||||||
|
{
|
||||||
|
return token_type::uninitialized;
|
||||||
|
}
|
||||||
|
while (i < avail && data[i] >= '0' && data[i] <= '9')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const std::size_t len = i;
|
||||||
|
|
||||||
|
// materialize the token exactly as scan_number() would, substituting the
|
||||||
|
// locale decimal point so convert_number()'s strtof fallback stays valid.
|
||||||
|
// reset() already cleared token_buffer, so append() fills it (assign() is
|
||||||
|
// avoided because custom string_t types need not provide it)
|
||||||
|
reset();
|
||||||
|
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
|
||||||
|
if (dot_index != std::string::npos)
|
||||||
|
{
|
||||||
|
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
|
||||||
|
decimal_point_position = dot_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume the remaining bytes of the number (current was already read)
|
||||||
|
ia.bulk_skip(len - 1);
|
||||||
|
position.chars_read_total += (len - 1);
|
||||||
|
position.chars_read_current_line += (len - 1);
|
||||||
|
|
||||||
|
return convert_number(number_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// contiguous input: try the number fast path, else the byte-path scanner
|
||||||
|
token_type scan_number_dispatch(std::true_type /*bulk*/)
|
||||||
|
{
|
||||||
|
const token_type t = scan_number_bulk_contiguous();
|
||||||
|
return (t != token_type::uninitialized) ? t : scan_number();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// streaming input: always use the byte-path scanner
|
||||||
|
token_type scan_number_dispatch(std::false_type /*bulk*/)
|
||||||
|
{
|
||||||
|
return scan_number();
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] literal_text the literal text to expect
|
@param[in] literal_text the literal text to expect
|
||||||
@param[in] length the length of the passed literal text
|
@param[in] length the length of the passed literal text
|
||||||
@@ -1680,7 +1882,7 @@ scan_number_done:
|
|||||||
case '7':
|
case '7':
|
||||||
case '8':
|
case '8':
|
||||||
case '9':
|
case '9':
|
||||||
return scan_number();
|
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {});
|
||||||
|
|
||||||
// end of input (the null byte is needed when parsing from
|
// end of input (the null byte is needed when parsing from
|
||||||
// string literals)
|
// string literals)
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
|
#include <cfloat> // FLT_EVAL_METHOD
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // int64_t, uint64_t
|
||||||
|
#include <limits> // numeric_limits
|
||||||
|
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// std::from_chars lives in <charconv>, but being in C++17 mode does not
|
||||||
|
// guarantee the header exists: GCC 7 sets __cplusplus to C++17 yet ships no
|
||||||
|
// <charconv> (added in GCC 8; floating-point support in GCC 11). Guard the
|
||||||
|
// include with __has_include so such toolchains fall back to the scalar path.
|
||||||
|
#if defined(JSON_HAS_CPP_17) && defined(__has_include)
|
||||||
|
#if __has_include(<charconv>)
|
||||||
|
#include <charconv> // from_chars (only used when __cpp_lib_to_chars is defined)
|
||||||
|
#include <system_error> // errc
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// This file contains the value-conversion helpers used by the lexer to turn an
|
||||||
|
// already-validated number token into a value, without the locale/errno
|
||||||
|
// overhead of std::strtoull/std::strtod. They are free functions so the lexer
|
||||||
|
// stays focused on scanning; see lexer::convert_number().
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief fast integer parser for an already-validated unsigned integer
|
||||||
|
|
||||||
|
The number scanner has already checked that [first, last) is a valid JSON
|
||||||
|
integer, so this only needs to accumulate the digits and detect overflow. This
|
||||||
|
avoids the locale/errno machinery of std::strtoull, which dominates
|
||||||
|
integer-heavy inputs.
|
||||||
|
|
||||||
|
@param[in] first pointer to the first character (a digit)
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[out] value the parsed value on success
|
||||||
|
@return true if the value fit into @a NumberUnsignedType; false on overflow, in
|
||||||
|
which case the caller falls back to floating-point parsing (matching the
|
||||||
|
previous std::strtoull behavior)
|
||||||
|
*/
|
||||||
|
template<typename NumberUnsignedType>
|
||||||
|
bool parse_integer_unsigned(const char* first, const char* last, NumberUnsignedType& value) noexcept
|
||||||
|
{
|
||||||
|
// accumulate in the widest unsigned type used by the previous strtoull
|
||||||
|
// path so the overflow behavior is unchanged for custom number types
|
||||||
|
std::uint64_t x = 0;
|
||||||
|
constexpr std::uint64_t cutoff = (std::numeric_limits<std::uint64_t>::max)() / 10u;
|
||||||
|
constexpr std::uint64_t cutlim = (std::numeric_limits<std::uint64_t>::max)() % 10u;
|
||||||
|
for (const char* p = first; p != last; ++p)
|
||||||
|
{
|
||||||
|
const auto digit = static_cast<std::uint64_t>(static_cast<unsigned char>(*p) - static_cast<unsigned char>('0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(x > cutoff || (x == cutoff && digit > cutlim)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
x = (x * 10u) + digit;
|
||||||
|
}
|
||||||
|
value = static_cast<NumberUnsignedType>(x);
|
||||||
|
// reject values that do not round-trip into a narrower NumberUnsignedType
|
||||||
|
return static_cast<std::uint64_t>(value) == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief fast integer parser for an already-validated negative integer
|
||||||
|
|
||||||
|
@param[in] first pointer to the leading '-'
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[out] value the parsed (negative) value on success
|
||||||
|
@return true on success; false on overflow (caller falls back to float)
|
||||||
|
*/
|
||||||
|
template<typename NumberIntegerType>
|
||||||
|
bool parse_integer_signed(const char* first, const char* last, NumberIntegerType& value) noexcept
|
||||||
|
{
|
||||||
|
// the state machine only reaches the signed path via a leading '-'
|
||||||
|
JSON_ASSERT(first != last && *first == '-');
|
||||||
|
std::uint64_t magnitude = 0;
|
||||||
|
// |INT64_MIN| == INT64_MAX + 1; this is the largest admissible magnitude
|
||||||
|
constexpr std::uint64_t limit = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1u;
|
||||||
|
for (const char* p = first + 1; p != last; ++p)
|
||||||
|
{
|
||||||
|
const auto digit = static_cast<std::uint64_t>(static_cast<unsigned char>(*p) - static_cast<unsigned char>('0'));
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(magnitude > (limit - digit) / 10u))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
magnitude = (magnitude * 10u) + digit;
|
||||||
|
}
|
||||||
|
const std::int64_t x = (magnitude == limit)
|
||||||
|
? (std::numeric_limits<std::int64_t>::min)()
|
||||||
|
: -static_cast<std::int64_t>(magnitude);
|
||||||
|
value = static_cast<NumberIntegerType>(x);
|
||||||
|
// reject values that do not round-trip into a narrower NumberIntegerType
|
||||||
|
return static_cast<std::int64_t>(value) == x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief exact fast path for parsing a `double` (Clinger's algorithm)
|
||||||
|
|
||||||
|
For the common case - at most 19 significant digits, a decimal exponent in
|
||||||
|
[-22, 22], and a significand below 2^53 - the value equals significand *
|
||||||
|
10^exp computed in IEEE-754 double arithmetic, which is exact under
|
||||||
|
round-to-nearest because both operands are exactly representable. This is the
|
||||||
|
same fast path used by fast_float/simdjson; the general cases are left to
|
||||||
|
std::strtod. The parser only activates for number_float_t == double; float and
|
||||||
|
long double keep the std::strtof/std::strtold paths (see the templated overload
|
||||||
|
below).
|
||||||
|
|
||||||
|
@param[in] first pointer to the first character of the number
|
||||||
|
@param[in] last pointer past the last character
|
||||||
|
@param[in] decimal_point the (locale-dependent) decimal point character
|
||||||
|
@param[out] out the parsed value on success
|
||||||
|
@return true if the value was parsed exactly; false to fall back to strtod
|
||||||
|
*/
|
||||||
|
template<typename DecimalPointType>
|
||||||
|
bool parse_float_fast(const char* first, const char* last, DecimalPointType decimal_point, double& out) noexcept
|
||||||
|
{
|
||||||
|
#if defined(FLT_EVAL_METHOD) && FLT_EVAL_METHOD != 0
|
||||||
|
// Clinger's fast path is only exact when double operations are evaluated in
|
||||||
|
// true double precision. On platforms that keep intermediates in extended
|
||||||
|
// precision (e.g. the x87 FPU on 32-bit x86, where FLT_EVAL_METHOD == 2) the
|
||||||
|
// single significand * 10^scale step is double-rounded and can be 1 ULP off,
|
||||||
|
// so decline and let the caller fall back to the correctly-rounded
|
||||||
|
// std::from_chars / std::strtod path.
|
||||||
|
static_cast<void>(first);
|
||||||
|
static_cast<void>(last);
|
||||||
|
static_cast<void>(decimal_point);
|
||||||
|
static_cast<void>(out);
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
static const std::array<double, 23> powers_of_ten =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11,
|
||||||
|
1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* p = first;
|
||||||
|
bool negative = false;
|
||||||
|
if (p != last && (*p == '-' || *p == '+'))
|
||||||
|
{
|
||||||
|
negative = (*p == '-');
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t significand = 0;
|
||||||
|
int num_digits = 0;
|
||||||
|
int fractional_digits = 0;
|
||||||
|
bool seen_dot = false;
|
||||||
|
bool any_digit = false;
|
||||||
|
for (; p != last; ++p)
|
||||||
|
{
|
||||||
|
const char c = *p;
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
any_digit = true;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(num_digits >= 19))
|
||||||
|
{
|
||||||
|
return false; // significand may not fit into uint64_t
|
||||||
|
}
|
||||||
|
significand = (significand * 10u) + static_cast<std::uint64_t>(c - '0');
|
||||||
|
++num_digits;
|
||||||
|
fractional_digits += static_cast<int>(seen_dot);
|
||||||
|
}
|
||||||
|
else if (static_cast<DecimalPointType>(c) == decimal_point)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(seen_dot))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen_dot = true;
|
||||||
|
}
|
||||||
|
else if (c == 'e' || c == 'E')
|
||||||
|
{
|
||||||
|
++p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!any_digit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int exponent = 0;
|
||||||
|
if (p != last) // an exponent part remains
|
||||||
|
{
|
||||||
|
bool exp_negative = false;
|
||||||
|
if (p != last && (*p == '-' || *p == '+'))
|
||||||
|
{
|
||||||
|
exp_negative = (*p == '-');
|
||||||
|
++p;
|
||||||
|
}
|
||||||
|
bool any_exp_digit = false;
|
||||||
|
for (; p != last; ++p)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(*p < '0' || *p > '9'))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
exponent = (exponent * 10) + (*p - '0');
|
||||||
|
any_exp_digit = true;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(exponent > 9999))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(!any_exp_digit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (exp_negative)
|
||||||
|
{
|
||||||
|
exponent = -exponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int scale = exponent - fractional_digits;
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(significand >= (static_cast<std::uint64_t>(1) << 53)))
|
||||||
|
{
|
||||||
|
return false; // significand not exactly representable as double
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = static_cast<double>(significand);
|
||||||
|
if (scale >= 0)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(scale > 22))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result *= powers_of_ten[static_cast<std::size_t>(scale)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(-scale > 22))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result /= powers_of_ten[static_cast<std::size_t>(-scale)];
|
||||||
|
}
|
||||||
|
out = negative ? -result : result;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// fast float path is only exact for `double`; decline for float/long double
|
||||||
|
template<typename DecimalPointType, typename FloatType>
|
||||||
|
bool parse_float_fast(const char* /*first*/, const char* /*last*/, DecimalPointType /*decimal_point*/, FloatType& /*out*/) noexcept
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief parse a float with std::from_chars (Eisel-Lemire) when available
|
||||||
|
|
||||||
|
std::from_chars is locale-independent, correctly rounded, and - via the
|
||||||
|
Eisel-Lemire algorithm in modern standard libraries - much faster than strtod
|
||||||
|
over the whole value range (not just the Clinger subset). It is used only when
|
||||||
|
__cpp_lib_to_chars indicates full floating-point support and only when it
|
||||||
|
consumes the entire token ([first, last)); a partial parse means the buffer
|
||||||
|
uses a non-'.' locale decimal point, in which case the caller falls back to the
|
||||||
|
locale-aware path. An under-/overflow (result_out_of_range) also declines, so
|
||||||
|
the caller's strtod fallback supplies the well-defined ±inf/0 result the parser
|
||||||
|
expects (side-stepping the P4168 divergence between implementations).
|
||||||
|
|
||||||
|
@return true if the value was parsed exactly and fully; false to fall back
|
||||||
|
*/
|
||||||
|
template<typename FloatType>
|
||||||
|
bool parse_float_from_chars(const char* first, const char* last, FloatType& out) noexcept
|
||||||
|
{
|
||||||
|
// JSON_HAS_CPP_17 must gate the use as well as the <charconv> include above:
|
||||||
|
// some standard libraries (e.g. libstdc++ 15) define __cpp_lib_to_chars even
|
||||||
|
// in C++14 mode, where <charconv> is not included.
|
||||||
|
#if defined(JSON_HAS_CPP_17) && defined(__cpp_lib_to_chars)
|
||||||
|
const auto result = std::from_chars(first, last, out);
|
||||||
|
return result.ec == std::errc() && result.ptr == last;
|
||||||
|
#else
|
||||||
|
static_cast<void>(first);
|
||||||
|
static_cast<void>(last);
|
||||||
|
static_cast<void>(out);
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | JSON for Modern C++
|
||||||
|
// | | |__ | | | | | | version 3.12.0
|
||||||
|
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||||
|
//
|
||||||
|
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint64_t
|
||||||
|
#include <cstring> // memcpy
|
||||||
|
|
||||||
|
#if defined(JSON_USE_SIMDUTF)
|
||||||
|
// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in
|
||||||
|
// external dependency: nlohmann/json itself stays header-only and the C++11
|
||||||
|
// scalar validator below is always available; defining JSON_USE_SIMDUTF
|
||||||
|
// additionally requires the simdutf headers on the include path and linking
|
||||||
|
// the simdutf library. See string_bulk_run().
|
||||||
|
#include <simdutf.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
|
// This file contains the byte-level string-scanning helpers used by the lexer's
|
||||||
|
// contiguous fast path. They operate purely on raw bytes (no dependency on the
|
||||||
|
// lexer's template parameters) so they are free functions, keeping the lexer
|
||||||
|
// itself focused on the state machine; see lexer::scan_string_bulk().
|
||||||
|
|
||||||
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
// classify a single byte as needing individual string handling: the closing
|
||||||
|
// quote, an escape, a control character, or a non-ASCII (UTF-8)
|
||||||
|
// lead/continuation byte. Ordinary bytes (0x20..0x7F except '"' and '\\') are
|
||||||
|
// copied verbatim, which the bulk scanner does 8 bytes at a time.
|
||||||
|
inline bool is_string_special(unsigned char c) noexcept
|
||||||
|
{
|
||||||
|
return c == '\"' || c == '\\' || c < 0x20u || c >= 0x80u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SWAR helper: return a word whose high bit is set in every byte of @a v that
|
||||||
|
// is_string_special(); zero if the 8 bytes are all ordinary.
|
||||||
|
inline std::uint64_t swar_string_special(std::uint64_t v) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||||
|
const std::uint64_t has_quote = (q - ones) & ~q & high;
|
||||||
|
const std::uint64_t has_backslash = (b - ones) & ~b & high;
|
||||||
|
const std::uint64_t has_control = (v - 0x2020202020202020ull) & ~v & high; // < 0x20
|
||||||
|
const std::uint64_t has_non_ascii = v & high; // >= 0x80
|
||||||
|
return has_quote | has_backslash | has_control | has_non_ascii;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the index of the first is_string_special() byte in [data, data+n), or
|
||||||
|
// n if every byte is ordinary; scans 8 bytes at a time
|
||||||
|
inline std::size_t find_string_special(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
std::uint64_t word = 0;
|
||||||
|
std::memcpy(&word, data + i, sizeof(word));
|
||||||
|
if (swar_string_special(word) != 0)
|
||||||
|
{
|
||||||
|
// a special byte is in this word; locate it (endian-agnostic)
|
||||||
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
if (is_string_special(data[i + j]))
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (is_string_special(data[i]))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// classify a byte as one the serializer must NOT copy verbatim when
|
||||||
|
// ensure_ascii is requested: the closing quote, an escape, a control character
|
||||||
|
// (< 0x20), DEL (0x7F), or any non-ASCII byte (>= 0x80). Everything else -
|
||||||
|
// printable ASCII except '"' and '\\' - is emitted unchanged. Note this differs
|
||||||
|
// from is_string_special() only in that 0x7F is also a stop (it is escaped as
|
||||||
|
// \u007f under ensure_ascii).
|
||||||
|
inline bool is_ascii_copyable(unsigned char c) noexcept
|
||||||
|
{
|
||||||
|
return c >= 0x20u && c < 0x7Fu && c != '\"' && c != '\\';
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the index of the first byte in [data, data+n) that is NOT
|
||||||
|
// is_ascii_copyable(), or n if every byte can be copied verbatim; scans 8 bytes
|
||||||
|
// at a time. Used by the serializer's ensure_ascii fast path.
|
||||||
|
inline std::size_t find_ascii_copyable_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull; // '"' (0x22)
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull; // '\\' (0x5C)
|
||||||
|
const std::uint64_t d = v ^ 0x7F7F7F7F7F7F7F7Full; // DEL (0x7F)
|
||||||
|
const std::uint64_t stop = ((q - ones) & ~q & high) // == '"'
|
||||||
|
| ((b - ones) & ~b & high) // == '\\'
|
||||||
|
| ((d - ones) & ~d & high) // == 0x7F
|
||||||
|
| ((v - 0x2020202020202020ull) & ~v & high) // < 0x20
|
||||||
|
| (v & high); // >= 0x80
|
||||||
|
if (stop != 0)
|
||||||
|
{
|
||||||
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
if (!is_ascii_copyable(data[i + j]))
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
if (!is_ascii_copyable(data[i]))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate one UTF-8 sequence at the front of [data, data+avail). Returns its
|
||||||
|
// length (2..4) only when the bytes form a *well-formed* sequence using exactly
|
||||||
|
// the same ranges as scan_string()'s per-byte switch, so the bulk path accepts
|
||||||
|
// precisely what the byte path accepts. Returns 0 for anything that is invalid,
|
||||||
|
// incomplete, or that the byte path must diagnose (the caller then defers to
|
||||||
|
// that path, keeping error messages unchanged). Lead bytes < 0x80 are handled
|
||||||
|
// by the caller and never passed here.
|
||||||
|
inline std::size_t validate_one_utf8(const unsigned char* data, std::size_t avail) noexcept
|
||||||
|
{
|
||||||
|
const unsigned char c0 = data[0];
|
||||||
|
if (c0 >= 0xC2 && c0 <= 0xDF) // U+0080..U+07FF
|
||||||
|
{
|
||||||
|
if (avail >= 2 && data[1] >= 0x80 && data[1] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xE0) // U+0800..U+0FFF
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0xA0 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((c0 >= 0xE1 && c0 <= 0xEC) || c0 == 0xEE || c0 == 0xEF) // U+1000..U+CFFF, U+E000..U+FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xED) // U+D000..U+D7FF (excludes surrogates)
|
||||||
|
{
|
||||||
|
if (avail >= 3 && data[1] >= 0x80 && data[1] <= 0x9F && data[2] >= 0x80 && data[2] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xF0) // U+10000..U+3FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x90 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 >= 0xF1 && c0 <= 0xF3) // U+40000..U+FFFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0xBF && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c0 == 0xF4) // U+100000..U+10FFFF
|
||||||
|
{
|
||||||
|
if (avail >= 4 && data[1] >= 0x80 && data[1] <= 0x8F && data[2] >= 0x80 && data[2] <= 0xBF && data[3] >= 0x80 && data[3] <= 0xBF)
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0; // invalid, incomplete, or must be diagnosed by the byte path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scalar (C++11) computation of the bulk run length: the number of leading
|
||||||
|
// bytes in [data, data+n) that are ordinary ASCII or complete well-formed UTF-8
|
||||||
|
// sequences, stopping before the first byte that needs individual handling (the
|
||||||
|
// closing quote, an escape, a control character, or an ill-formed/truncated
|
||||||
|
// sequence). ASCII is skipped 8 bytes at a time.
|
||||||
|
inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
std::size_t pos = 0;
|
||||||
|
while (pos < n)
|
||||||
|
{
|
||||||
|
pos += find_string_special(data + pos, n - pos);
|
||||||
|
if (pos >= n || data[pos] < 0x80u)
|
||||||
|
{
|
||||||
|
break; // end of buffer, or a quote/escape/control byte
|
||||||
|
}
|
||||||
|
const std::size_t seq = validate_one_utf8(data + pos, n - pos);
|
||||||
|
if (seq == 0)
|
||||||
|
{
|
||||||
|
break; // ill-formed or truncated: let the byte path diagnose it
|
||||||
|
}
|
||||||
|
pos += seq;
|
||||||
|
}
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(JSON_USE_SIMDUTF)
|
||||||
|
// Index of the first quote/escape/control byte in [data, data+n) (non-ASCII
|
||||||
|
// bytes are *not* stops here - the whole run is handed to simdutf), or n.
|
||||||
|
inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
constexpr std::uint64_t ones = 0x0101010101010101ull;
|
||||||
|
constexpr std::uint64_t high = 0x8080808080808080ull;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for (; i + 8 <= n; i += 8)
|
||||||
|
{
|
||||||
|
std::uint64_t v = 0;
|
||||||
|
std::memcpy(&v, data + i, sizeof(v));
|
||||||
|
const std::uint64_t q = v ^ 0x2222222222222222ull;
|
||||||
|
const std::uint64_t b = v ^ 0x5C5C5C5C5C5C5C5Cull;
|
||||||
|
const std::uint64_t hit = ((q - ones) & ~q & high)
|
||||||
|
| ((b - ones) & ~b & high)
|
||||||
|
| ((v - 0x2020202020202020ull) & ~v & high);
|
||||||
|
if (hit != 0)
|
||||||
|
{
|
||||||
|
for (std::size_t j = 0; j < 8; ++j)
|
||||||
|
{
|
||||||
|
const unsigned char c = data[i + j];
|
||||||
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||||
|
{
|
||||||
|
return i + j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; i < n; ++i)
|
||||||
|
{
|
||||||
|
const unsigned char c = data[i];
|
||||||
|
if (c == '\"' || c == '\\' || c < 0x20u)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Backend-dispatched bulk run length. With JSON_USE_SIMDUTF the run up to the
|
||||||
|
// next delimiter is validated in one shot by simdutf; on the rare failure the
|
||||||
|
// scalar helper recomputes the exact valid prefix so the byte path still
|
||||||
|
// produces the precise diagnostic. Without it, the pure scalar path is used.
|
||||||
|
inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept
|
||||||
|
{
|
||||||
|
#if defined(JSON_USE_SIMDUTF)
|
||||||
|
const std::size_t run = find_string_delimiter(data, n);
|
||||||
|
if (run != 0 && simdutf::validate_utf8(reinterpret_cast<const char*>(data), run))
|
||||||
|
{
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
return scalar_string_bulk_run(data, n);
|
||||||
|
#else
|
||||||
|
return scalar_string_bulk_run(data, n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
@@ -979,28 +979,13 @@ class binary_writer
|
|||||||
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Checks that @a size fits into the 32-bit length field used by BSON
|
|
||||||
@return The size as a signed 32-bit integer
|
|
||||||
@throw out_of_range.412 if @a size exceeds the range of std::int32_t
|
|
||||||
*/
|
|
||||||
static std::int32_t to_bson_length(const std::size_t size)
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
|
|
||||||
{
|
|
||||||
JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<std::int32_t>(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Writes the given @a element_type and @a name to the output adapter
|
@brief Writes the given @a element_type and @a name to the output adapter
|
||||||
*/
|
*/
|
||||||
void write_bson_entry_header(const string_t& name,
|
void write_bson_entry_header(const string_t& name,
|
||||||
const std::uint8_t element_type)
|
const std::uint8_t element_type)
|
||||||
{
|
{
|
||||||
oa->write_character(to_char_type(element_type));
|
oa->write_character(to_char_type(element_type)); // boolean
|
||||||
oa->write_characters(
|
oa->write_characters(
|
||||||
reinterpret_cast<const CharType*>(name.c_str()),
|
reinterpret_cast<const CharType*>(name.c_str()),
|
||||||
name.size() + 1u);
|
name.size() + 1u);
|
||||||
@@ -1042,7 +1027,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x02);
|
write_bson_entry_header(name, 0x02);
|
||||||
|
|
||||||
write_number<std::int32_t>(to_bson_length(value.size() + 1ul), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);
|
||||||
oa->write_characters(
|
oa->write_characters(
|
||||||
reinterpret_cast<const CharType*>(value.c_str()),
|
reinterpret_cast<const CharType*>(value.c_str()),
|
||||||
value.size() + 1);
|
value.size() + 1);
|
||||||
@@ -1085,7 +1070,7 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return The size of the BSON-encoded unsigned integer @a value
|
@return The size of the BSON-encoded unsigned integer in @a j
|
||||||
*/
|
*/
|
||||||
static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
|
static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept
|
||||||
{
|
{
|
||||||
@@ -1098,22 +1083,22 @@ class binary_writer
|
|||||||
@brief Writes a BSON element with key @a name and unsigned @a value
|
@brief Writes a BSON element with key @a name and unsigned @a value
|
||||||
*/
|
*/
|
||||||
void write_bson_unsigned(const string_t& name,
|
void write_bson_unsigned(const string_t& name,
|
||||||
const std::uint64_t value)
|
const BasicJsonType& j)
|
||||||
{
|
{
|
||||||
if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x10 /* int32 */);
|
write_bson_entry_header(name, 0x10 /* int32 */);
|
||||||
write_number<std::int32_t>(static_cast<std::int32_t>(value), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true);
|
||||||
}
|
}
|
||||||
else if (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x12 /* int64 */);
|
write_bson_entry_header(name, 0x12 /* int64 */);
|
||||||
write_number<std::int64_t>(static_cast<std::int64_t>(value), true);
|
write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x11 /* uint64 */);
|
write_bson_entry_header(name, 0x11 /* uint64 */);
|
||||||
write_number<std::uint64_t>(value, true);
|
write_number<std::uint64_t>(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1157,7 +1142,7 @@ class binary_writer
|
|||||||
const typename BasicJsonType::array_t& value)
|
const typename BasicJsonType::array_t& value)
|
||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x04); // array
|
write_bson_entry_header(name, 0x04); // array
|
||||||
write_number<std::int32_t>(to_bson_length(calc_bson_array_size(value)), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);
|
||||||
|
|
||||||
std::size_t array_index = 0ul;
|
std::size_t array_index = 0ul;
|
||||||
|
|
||||||
@@ -1177,7 +1162,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
write_bson_entry_header(name, 0x05);
|
write_bson_entry_header(name, 0x05);
|
||||||
|
|
||||||
write_number<std::int32_t>(to_bson_length(value.size()), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);
|
||||||
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));
|
||||||
|
|
||||||
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
|
||||||
@@ -1259,7 +1244,7 @@ class binary_writer
|
|||||||
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
return write_bson_integer(name, j.m_data.m_value.number_integer);
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
return write_bson_unsigned(name, j.m_data.m_value.number_unsigned);
|
return write_bson_unsigned(name, j);
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
return write_bson_string(name, *j.m_data.m_value.string);
|
return write_bson_string(name, *j.m_data.m_value.string);
|
||||||
@@ -1299,7 +1284,7 @@ class binary_writer
|
|||||||
*/
|
*/
|
||||||
void write_bson_object(const typename BasicJsonType::object_t& value)
|
void write_bson_object(const typename BasicJsonType::object_t& value)
|
||||||
{
|
{
|
||||||
write_number<std::int32_t>(to_bson_length(calc_bson_object_size(value)), true);
|
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);
|
||||||
|
|
||||||
for (const auto& el : value)
|
for (const auto& el : value)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <cstddef> // size_t, ptrdiff_t
|
#include <cstddef> // size_t, ptrdiff_t
|
||||||
#include <cstdint> // uint8_t
|
#include <cstdint> // uint8_t
|
||||||
#include <cstdio> // snprintf
|
#include <cstdio> // snprintf
|
||||||
|
#include <cstring> // memcpy
|
||||||
#include <limits> // numeric_limits
|
#include <limits> // numeric_limits
|
||||||
#include <string> // string, char_traits
|
#include <string> // string, char_traits
|
||||||
#include <iomanip> // setfill, setw
|
#include <iomanip> // setfill, setw
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
|
|
||||||
#include <nlohmann/detail/conversions/to_chars.hpp>
|
#include <nlohmann/detail/conversions/to_chars.hpp>
|
||||||
#include <nlohmann/detail/exceptions.hpp>
|
#include <nlohmann/detail/exceptions.hpp>
|
||||||
|
#include <nlohmann/detail/input/string_scan.hpp>
|
||||||
#include <nlohmann/detail/macro_scope.hpp>
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||||
@@ -109,6 +111,25 @@ class serializer
|
|||||||
const bool ensure_ascii,
|
const bool ensure_ascii,
|
||||||
const unsigned int indent_step,
|
const unsigned int indent_step,
|
||||||
const unsigned int current_indent = 0)
|
const unsigned int current_indent = 0)
|
||||||
|
{
|
||||||
|
dump_internal(val, pretty_print, ensure_ascii, indent_step, current_indent);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
/*!
|
||||||
|
@brief recursive worker for @ref dump
|
||||||
|
|
||||||
|
Identical in behavior to the historical @ref dump, but writes into the
|
||||||
|
serializer's internal @ref write_buffer instead of issuing a virtual call
|
||||||
|
per token. The public @ref dump wraps this and flushes the buffer once the
|
||||||
|
top-level value has been serialized.
|
||||||
|
*/
|
||||||
|
void dump_internal(const BasicJsonType& val,
|
||||||
|
const bool pretty_print,
|
||||||
|
const bool ensure_ascii,
|
||||||
|
const unsigned int indent_step,
|
||||||
|
const unsigned int current_indent = 0)
|
||||||
{
|
{
|
||||||
switch (val.m_data.m_type)
|
switch (val.m_data.m_type)
|
||||||
{
|
{
|
||||||
@@ -116,13 +137,13 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.object->empty())
|
if (val.m_data.m_value.object->empty())
|
||||||
{
|
{
|
||||||
o->write_characters("{}", 2);
|
put_chars("{}", 2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
o->write_characters("{\n", 2);
|
put_chars("{\n", 2);
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = current_indent + indent_step;
|
||||||
@@ -135,51 +156,51 @@ class serializer
|
|||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
o->write_characters("\": ", 3);
|
put_chars("\": ", 3);
|
||||||
dump(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
o->write_characters(",\n", 2);
|
put_chars(",\n", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
o->write_characters("\": ", 3);
|
put_chars("\": ", 3);
|
||||||
dump(i->second, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(i->second, true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
o->write_character('\n');
|
put_char('\n');
|
||||||
o->write_characters(indent_string.c_str(), current_indent);
|
put_chars(indent_string.c_str(), current_indent);
|
||||||
o->write_character('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_character('{');
|
put_char('{');
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
auto i = val.m_data.m_value.object->cbegin();
|
auto i = val.m_data.m_value.object->cbegin();
|
||||||
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)
|
||||||
{
|
{
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
o->write_characters("\":", 2);
|
put_chars("\":", 2);
|
||||||
dump(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
o->write_character(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
JSON_ASSERT(i != val.m_data.m_value.object->cend());
|
||||||
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
dump_escaped(i->first, ensure_ascii);
|
dump_escaped(i->first, ensure_ascii);
|
||||||
o->write_characters("\":", 2);
|
put_chars("\":", 2);
|
||||||
dump(i->second, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(i->second, false, ensure_ascii, indent_step, current_indent);
|
||||||
|
|
||||||
o->write_character('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -189,13 +210,13 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.array->empty())
|
if (val.m_data.m_value.array->empty())
|
||||||
{
|
{
|
||||||
o->write_characters("[]", 2);
|
put_chars("[]", 2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
o->write_characters("[\n", 2);
|
put_chars("[\n", 2);
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = current_indent + indent_step;
|
||||||
@@ -208,37 +229,37 @@ class serializer
|
|||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
dump(*i, true, ensure_ascii, indent_step, new_indent);
|
dump_internal(*i, true, ensure_ascii, indent_step, new_indent);
|
||||||
o->write_characters(",\n", 2);
|
put_chars(",\n", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
dump_internal(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);
|
||||||
|
|
||||||
o->write_character('\n');
|
put_char('\n');
|
||||||
o->write_characters(indent_string.c_str(), current_indent);
|
put_chars(indent_string.c_str(), current_indent);
|
||||||
o->write_character(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_character('[');
|
put_char('[');
|
||||||
|
|
||||||
// first n-1 elements
|
// first n-1 elements
|
||||||
for (auto i = val.m_data.m_value.array->cbegin();
|
for (auto i = val.m_data.m_value.array->cbegin();
|
||||||
i != val.m_data.m_value.array->cend() - 1; ++i)
|
i != val.m_data.m_value.array->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump(*i, false, ensure_ascii, indent_step, current_indent);
|
dump_internal(*i, false, ensure_ascii, indent_step, current_indent);
|
||||||
o->write_character(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
// last element
|
// last element
|
||||||
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
JSON_ASSERT(!val.m_data.m_value.array->empty());
|
||||||
dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
|
dump_internal(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);
|
||||||
|
|
||||||
o->write_character(']');
|
put_char(']');
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -246,9 +267,9 @@ class serializer
|
|||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
dump_escaped(*val.m_data.m_value.string, ensure_ascii);
|
dump_escaped(*val.m_data.m_value.string, ensure_ascii);
|
||||||
o->write_character('\"');
|
put_char('\"');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +277,7 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (pretty_print)
|
if (pretty_print)
|
||||||
{
|
{
|
||||||
o->write_characters("{\n", 2);
|
put_chars("{\n", 2);
|
||||||
|
|
||||||
// variable to hold indentation for recursive calls
|
// variable to hold indentation for recursive calls
|
||||||
const auto new_indent = current_indent + indent_step;
|
const auto new_indent = current_indent + indent_step;
|
||||||
@@ -265,9 +286,9 @@ class serializer
|
|||||||
indent_string.resize(indent_string.size() * 2, ' ');
|
indent_string.resize(indent_string.size() * 2, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
|
|
||||||
o->write_characters("\"bytes\": [", 10);
|
put_chars("\"bytes\": [", 10);
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -275,30 +296,30 @@ class serializer
|
|||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_integer(*i);
|
dump_integer(*i);
|
||||||
o->write_characters(", ", 2);
|
put_chars(", ", 2);
|
||||||
}
|
}
|
||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
o->write_characters("],\n", 3);
|
put_chars("],\n", 3);
|
||||||
o->write_characters(indent_string.c_str(), new_indent);
|
put_chars(indent_string.c_str(), new_indent);
|
||||||
|
|
||||||
o->write_characters("\"subtype\": ", 11);
|
put_chars("\"subtype\": ", 11);
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_characters("null", 4);
|
put_chars("null", 4);
|
||||||
}
|
}
|
||||||
o->write_character('\n');
|
put_char('\n');
|
||||||
o->write_characters(indent_string.c_str(), current_indent);
|
put_chars(indent_string.c_str(), current_indent);
|
||||||
o->write_character('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_characters("{\"bytes\":[", 10);
|
put_chars("{\"bytes\":[", 10);
|
||||||
|
|
||||||
if (!val.m_data.m_value.binary->empty())
|
if (!val.m_data.m_value.binary->empty())
|
||||||
{
|
{
|
||||||
@@ -306,20 +327,20 @@ class serializer
|
|||||||
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
i != val.m_data.m_value.binary->cend() - 1; ++i)
|
||||||
{
|
{
|
||||||
dump_integer(*i);
|
dump_integer(*i);
|
||||||
o->write_character(',');
|
put_char(',');
|
||||||
}
|
}
|
||||||
dump_integer(val.m_data.m_value.binary->back());
|
dump_integer(val.m_data.m_value.binary->back());
|
||||||
}
|
}
|
||||||
|
|
||||||
o->write_characters("],\"subtype\":", 12);
|
put_chars("],\"subtype\":", 12);
|
||||||
if (val.m_data.m_value.binary->has_subtype())
|
if (val.m_data.m_value.binary->has_subtype())
|
||||||
{
|
{
|
||||||
dump_integer(val.m_data.m_value.binary->subtype());
|
dump_integer(val.m_data.m_value.binary->subtype());
|
||||||
o->write_character('}');
|
put_char('}');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_characters("null}", 5);
|
put_chars("null}", 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -329,11 +350,11 @@ class serializer
|
|||||||
{
|
{
|
||||||
if (val.m_data.m_value.boolean)
|
if (val.m_data.m_value.boolean)
|
||||||
{
|
{
|
||||||
o->write_characters("true", 4);
|
put_chars("true", 4);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_characters("false", 5);
|
put_chars("false", 5);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -358,13 +379,13 @@ class serializer
|
|||||||
|
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
{
|
{
|
||||||
o->write_characters("<discarded>", 11);
|
put_chars("<discarded>", 11);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
{
|
{
|
||||||
o->write_characters("null", 4);
|
put_chars("null", 4);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,6 +421,45 @@ class serializer
|
|||||||
|
|
||||||
for (std::size_t i = 0; i < s.size(); ++i)
|
for (std::size_t i = 0; i < s.size(); ++i)
|
||||||
{
|
{
|
||||||
|
// Fast path: at a character boundary (state == UTF8_ACCEPT),
|
||||||
|
// bulk-copy the longest run of bytes that need no escaping using a
|
||||||
|
// SWAR scanner shared with the lexer's contiguous path. The scanner
|
||||||
|
// stops exactly at the first byte dump_escaped would handle
|
||||||
|
// individually, so that byte is left to the byte-at-a-time path
|
||||||
|
// below, keeping escaping output and error diagnostics unchanged.
|
||||||
|
//
|
||||||
|
// - ensure_ascii == false: string_bulk_run() copies ordinary bytes
|
||||||
|
// and complete well-formed UTF-8, stopping at a quote, backslash,
|
||||||
|
// control character (< 0x20), or ill-formed/truncated sequence.
|
||||||
|
// - ensure_ascii == true: only printable ASCII may be copied
|
||||||
|
// verbatim; find_ascii_copyable_run() additionally stops at 0x7F
|
||||||
|
// and every non-ASCII byte (>= 0x80), which must be \u-escaped.
|
||||||
|
if (state == UTF8_ACCEPT)
|
||||||
|
{
|
||||||
|
const auto* const data = reinterpret_cast<const unsigned char*>(s.data());
|
||||||
|
const std::size_t run = ensure_ascii
|
||||||
|
? find_ascii_copyable_run(data + i, s.size() - i)
|
||||||
|
: string_bulk_run(data + i, s.size() - i);
|
||||||
|
if (run != 0)
|
||||||
|
{
|
||||||
|
// emit any bytes still pending in string_buffer first to
|
||||||
|
// preserve output order, then write the run directly
|
||||||
|
if (bytes != 0)
|
||||||
|
{
|
||||||
|
put_chars(string_buffer.data(), bytes);
|
||||||
|
bytes = 0;
|
||||||
|
}
|
||||||
|
put_chars(s.data() + i, run);
|
||||||
|
bytes_after_last_accept = 0;
|
||||||
|
undumped_chars = 0;
|
||||||
|
i += run;
|
||||||
|
if (i >= s.size())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto byte = static_cast<std::uint8_t>(s[i]);
|
const auto byte = static_cast<std::uint8_t>(s[i]);
|
||||||
|
|
||||||
switch (decode(state, codepoint, byte))
|
switch (decode(state, codepoint, byte))
|
||||||
@@ -488,7 +548,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
o->write_characters(string_buffer.data(), bytes);
|
put_chars(string_buffer.data(), bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +607,7 @@ class serializer
|
|||||||
// written ("\uxxxx\uxxxx\0") for one code point
|
// written ("\uxxxx\uxxxx\0") for one code point
|
||||||
if (string_buffer.size() - bytes < 13)
|
if (string_buffer.size() - bytes < 13)
|
||||||
{
|
{
|
||||||
o->write_characters(string_buffer.data(), bytes);
|
put_chars(string_buffer.data(), bytes);
|
||||||
bytes = 0;
|
bytes = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,7 +646,7 @@ class serializer
|
|||||||
// write buffer
|
// write buffer
|
||||||
if (bytes > 0)
|
if (bytes > 0)
|
||||||
{
|
{
|
||||||
o->write_characters(string_buffer.data(), bytes);
|
put_chars(string_buffer.data(), bytes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -602,22 +662,22 @@ class serializer
|
|||||||
case error_handler_t::ignore:
|
case error_handler_t::ignore:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
o->write_characters(string_buffer.data(), bytes_after_last_accept);
|
put_chars(string_buffer.data(), bytes_after_last_accept);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case error_handler_t::replace:
|
case error_handler_t::replace:
|
||||||
{
|
{
|
||||||
// write all accepted bytes
|
// write all accepted bytes
|
||||||
o->write_characters(string_buffer.data(), bytes_after_last_accept);
|
put_chars(string_buffer.data(), bytes_after_last_accept);
|
||||||
// add a replacement character
|
// add a replacement character
|
||||||
if (ensure_ascii)
|
if (ensure_ascii)
|
||||||
{
|
{
|
||||||
o->write_characters("\\ufffd", 6);
|
put_chars("\\ufffd", 6);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
o->write_characters("\xEF\xBF\xBD", 3);
|
put_chars("\xEF\xBF\xBD", 3);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -628,6 +688,66 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
@brief append a single character to the write buffer
|
||||||
|
|
||||||
|
Structural characters ('{', '"', ',', ...) previously went straight to the
|
||||||
|
output adapter, one virtual call each. Buffering them and flushing in bulk
|
||||||
|
turns those many indirect calls into a single memcpy plus an occasional
|
||||||
|
flush, which dominates the cost of serializing object/array-heavy values.
|
||||||
|
*/
|
||||||
|
void put_char(char c)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos == write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
write_buffer[write_buffer_pos++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief append @a length characters to the write buffer
|
||||||
|
|
||||||
|
Runs that do not fit the buffer are written straight through the output
|
||||||
|
adapter (after flushing what is pending), so large string/number payloads
|
||||||
|
are not copied an extra time.
|
||||||
|
*/
|
||||||
|
JSON_HEDLEY_NON_NULL(2)
|
||||||
|
void put_chars(const char* s, std::size_t length)
|
||||||
|
{
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(length >= write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
o->write_characters(s, length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(write_buffer_pos + length > write_buffer.size()))
|
||||||
|
{
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
std::memcpy(write_buffer.data() + write_buffer_pos, s, length);
|
||||||
|
write_buffer_pos += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON_PRIVATE_UNLESS_TESTED:
|
||||||
|
/*!
|
||||||
|
@brief flush the write buffer to the output adapter
|
||||||
|
|
||||||
|
Writing zero characters is a well-defined no-op for every output adapter, so
|
||||||
|
the buffered length is passed through unconditionally (no empty-guard branch
|
||||||
|
to leave uncovered).
|
||||||
|
|
||||||
|
@note dump_escaped() and dump_integer()/dump_float() write into the internal
|
||||||
|
write buffer; callers that invoke them directly (rather than through the
|
||||||
|
public dump()) must call flush() before inspecting the output.
|
||||||
|
*/
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
o->write_characters(write_buffer.data(), write_buffer_pos);
|
||||||
|
write_buffer_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
/*!
|
||||||
@brief count digits
|
@brief count digits
|
||||||
@@ -752,7 +872,7 @@ class serializer
|
|||||||
// special case for "0"
|
// special case for "0"
|
||||||
if (x == 0)
|
if (x == 0)
|
||||||
{
|
{
|
||||||
o->write_character('0');
|
put_char('0');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -805,7 +925,7 @@ class serializer
|
|||||||
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
*(--buffer_ptr) = static_cast<char>('0' + abs_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
o->write_characters(number_buffer.data(), n_chars);
|
put_chars(number_buffer.data(), n_chars);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -821,7 +941,7 @@ class serializer
|
|||||||
// NaN / inf
|
// NaN / inf
|
||||||
if (!std::isfinite(x))
|
if (!std::isfinite(x))
|
||||||
{
|
{
|
||||||
o->write_characters("null", 4);
|
put_chars("null", 4);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,7 +962,7 @@ class serializer
|
|||||||
auto* begin = number_buffer.data();
|
auto* begin = number_buffer.data();
|
||||||
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);
|
||||||
|
|
||||||
o->write_characters(begin, static_cast<size_t>(end - begin));
|
put_chars(begin, static_cast<size_t>(end - begin));
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON_HEDLEY_NON_NULL(1)
|
JSON_HEDLEY_NON_NULL(1)
|
||||||
@@ -893,7 +1013,7 @@ class serializer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));
|
put_chars(number_buffer.data(), static_cast<std::size_t>(len));
|
||||||
|
|
||||||
// determine if we need to append ".0"
|
// determine if we need to append ".0"
|
||||||
const bool value_is_int_like =
|
const bool value_is_int_like =
|
||||||
@@ -905,7 +1025,7 @@ class serializer
|
|||||||
|
|
||||||
if (value_is_int_like)
|
if (value_is_int_like)
|
||||||
{
|
{
|
||||||
o->write_characters(".0", 2);
|
put_chars(".0", 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,6 +1135,12 @@ class serializer
|
|||||||
|
|
||||||
/// error_handler how to react on decoding errors
|
/// error_handler how to react on decoding errors
|
||||||
const error_handler_t error_handler;
|
const error_handler_t error_handler;
|
||||||
|
|
||||||
|
/// buffer collecting output before it is flushed to the output adapter, so
|
||||||
|
/// that the many small structural writes become few bulk writes
|
||||||
|
std::array<char, 1024> write_buffer{{}};
|
||||||
|
/// number of valid bytes currently held in @ref write_buffer
|
||||||
|
std::size_t write_buffer_pos = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|||||||
+1106
-193
File diff suppressed because it is too large
Load Diff
@@ -2721,19 +2721,6 @@ TEST_CASE("BJData")
|
|||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("negative length")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vi = {'S', 'i', 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_bjdata(vi, true, false).is_discarded());
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_bjdata(vl, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("parse bjdata markers in ubjson")
|
SECTION("parse bjdata markers in ubjson")
|
||||||
{
|
{
|
||||||
// create a single-character string for all number types
|
// create a single-character string for all number types
|
||||||
|
|||||||
@@ -11,35 +11,12 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <vector>
|
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
// a binary container that reports a size beyond INT32_MAX without allocating
|
|
||||||
// that much memory, so the BSON length overflow can be tested cheaply
|
|
||||||
class huge_binary_t : public std::vector<std::uint8_t>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using std::vector<std::uint8_t>::vector;
|
|
||||||
|
|
||||||
size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
|
|
||||||
{
|
|
||||||
// one byte more than the BSON length field can represent
|
|
||||||
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using huge_binary_json = nlohmann::basic_json <
|
|
||||||
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
|
|
||||||
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE("BSON")
|
TEST_CASE("BSON")
|
||||||
{
|
{
|
||||||
SECTION("individual values not supported")
|
SECTION("individual values not supported")
|
||||||
@@ -103,14 +80,6 @@ TEST_CASE("BSON")
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
|
|
||||||
{
|
|
||||||
huge_binary_json j;
|
|
||||||
j["b"] = huge_binary_json::binary(huge_binary_t{});
|
|
||||||
|
|
||||||
CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("string length must be at least 1")
|
SECTION("string length must be at least 1")
|
||||||
{
|
{
|
||||||
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
|
||||||
@@ -885,62 +854,6 @@ TEST_CASE("Unsupported BSON input")
|
|||||||
CHECK(!json::sax_parse(bson, &scp, json::input_format_t::bson));
|
CHECK(!json::sax_parse(bson, &scp, json::input_format_t::bson));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("BSON document size mismatch")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
|
|
||||||
SECTION("top-level document declaring more bytes than it contains")
|
|
||||||
{
|
|
||||||
// empty object, but the length prefix claims 6 bytes instead of 5
|
|
||||||
std::vector<std::uint8_t> const input = {0x06, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)", json::parse_error&);
|
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("top-level document with a negative size")
|
|
||||||
{
|
|
||||||
std::vector<std::uint8_t> const input = {0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size -1 does not match the number of bytes read (5)", json::parse_error&);
|
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("embedded document whose size disagrees with its terminator")
|
|
||||||
{
|
|
||||||
// the embedded document "d" declares 0x7FFFFFFF bytes but its 0x00
|
|
||||||
// terminator falls right after {"a":null}; the length prefix would
|
|
||||||
// otherwise let the following "h" element be read as a member of the
|
|
||||||
// enclosing document instead of "d"
|
|
||||||
std::vector<std::uint8_t> const input =
|
|
||||||
{
|
|
||||||
0x00, 0x00, 0x00, 0x00, // outer size
|
|
||||||
0x03, 'd', 0x00, // entry: embedded document "d"
|
|
||||||
0xFF, 0xFF, 0xFF, 0x7F, // embedded size 0x7FFFFFFF
|
|
||||||
0x0A, 'a', 0x00, // entry: null "a"
|
|
||||||
0x00, // embedded end marker
|
|
||||||
0x08, 'h', 0x00, 0x01, // entry: bool "h" = true
|
|
||||||
0x00 // outer end marker
|
|
||||||
};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 15: syntax error while parsing BSON document: document size 2147483647 does not match the number of bytes read (8)", json::parse_error&);
|
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("embedded array whose size disagrees with its terminator")
|
|
||||||
{
|
|
||||||
// array [42] is 12 bytes, but the length prefix claims 13
|
|
||||||
std::vector<std::uint8_t> const input =
|
|
||||||
{
|
|
||||||
0x00, 0x00, 0x00, 0x00, // outer size
|
|
||||||
0x04, 'a', 0x00, // entry: array "a"
|
|
||||||
0x0D, 0x00, 0x00, 0x00, // array size 13 (real is 12)
|
|
||||||
0x10, '0', 0x00, 0x2A, 0x00, 0x00, 0x00, // entry: int32 "0" = 42
|
|
||||||
0x00, // array end marker
|
|
||||||
0x00 // outer end marker
|
|
||||||
};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_bson(input), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 13 does not match the number of bytes read (12)", json::parse_error&);
|
|
||||||
CHECK(json::from_bson(input, true, false).is_discarded());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("BSON numerical data")
|
TEST_CASE("BSON numerical data")
|
||||||
{
|
{
|
||||||
SECTION("number")
|
SECTION("number")
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <sstream> // stringstream
|
||||||
|
#include <string> // string
|
||||||
|
#include <vector> // vector
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
// shortcut to scan a string literal
|
// shortcut to scan a string literal
|
||||||
@@ -224,3 +228,75 @@ TEST_CASE("lexer class")
|
|||||||
CHECK((scan_string("/**//**//**/", true) == json::lexer::token_type::end_of_input));
|
CHECK((scan_string("/**//**//**/", true) == json::lexer::token_type::end_of_input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("lexer number fast path")
|
||||||
|
{
|
||||||
|
// The contiguous fast path (used for pointer/string input) must agree with
|
||||||
|
// the streaming byte path (used for std::istream) on token type, numeric
|
||||||
|
// value, and round-trip text for every well-formed number, and reject the
|
||||||
|
// same malformed numbers with the same message.
|
||||||
|
SECTION("contiguous vs streaming parity")
|
||||||
|
{
|
||||||
|
const std::vector<std::string> numbers =
|
||||||
|
{
|
||||||
|
"0", "-0", "1", "-1", "42", "-42", "10", "100", "1234567890",
|
||||||
|
"0.0", "-0.0", "3.14", "-3.14", "0.5", "-0.001", "123.456789",
|
||||||
|
"1e0", "1E0", "1e10", "1e-10", "1e+10", "1.5e3", "-2.5E-4",
|
||||||
|
"9223372036854775807", // INT64_MAX -> unsigned
|
||||||
|
"9223372036854775808", // INT64_MAX + 1 -> unsigned
|
||||||
|
"18446744073709551615", // UINT64_MAX -> unsigned
|
||||||
|
"18446744073709551616", // UINT64_MAX + 1 -> float
|
||||||
|
"-9223372036854775808", // INT64_MIN -> integer
|
||||||
|
"-9223372036854775809", // INT64_MIN - 1 -> float
|
||||||
|
"123456789012345678901234567890", // huge -> float
|
||||||
|
"0.30000000000000004", "2.2250738585072014e-308", "1e308",
|
||||||
|
// high-precision / wide-exponent values that exercise the
|
||||||
|
// std::from_chars (Eisel-Lemire) path beyond the Clinger subset
|
||||||
|
"1.7976931348623157e308", "1.2345678901234567e-250",
|
||||||
|
"9007199254740993", "5e-324", "1e-320"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& n : numbers)
|
||||||
|
{
|
||||||
|
const std::string doc = "[" + n + "]";
|
||||||
|
|
||||||
|
// contiguous fast path
|
||||||
|
const json a = json::parse(doc);
|
||||||
|
// streaming byte path
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
const json b = json::parse(ss);
|
||||||
|
|
||||||
|
CAPTURE(n);
|
||||||
|
CHECK(a == b);
|
||||||
|
CHECK(a.dump() == b.dump());
|
||||||
|
CHECK(a[0].type() == b[0].type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("token type classification")
|
||||||
|
{
|
||||||
|
CHECK((scan_string("0") == json::lexer::token_type::value_unsigned));
|
||||||
|
CHECK((scan_string("-1") == json::lexer::token_type::value_integer));
|
||||||
|
CHECK((scan_string("1.5") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("1e5") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("18446744073709551615") == json::lexer::token_type::value_unsigned));
|
||||||
|
CHECK((scan_string("18446744073709551616") == json::lexer::token_type::value_float));
|
||||||
|
CHECK((scan_string("-9223372036854775808") == json::lexer::token_type::value_integer));
|
||||||
|
CHECK((scan_string("-9223372036854775809") == json::lexer::token_type::value_float));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("malformed numbers are rejected identically")
|
||||||
|
{
|
||||||
|
for (const char* bad :
|
||||||
|
{"-", "1.", "1e", "1e+", "1.2e", "01", "-01", "1..2", "1.2.3"
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(bad);
|
||||||
|
// the contiguous fast path must decline and let the byte path report
|
||||||
|
const std::string doc = std::string("[") + bad + "]";
|
||||||
|
CHECK_FALSE(json::accept(doc));
|
||||||
|
std::stringstream ss(doc);
|
||||||
|
CHECK_FALSE(json::accept(ss));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ void check_escaped(const char* original, const char* escaped, const bool ensure_
|
|||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
json::serializer s(nlohmann::detail::output_adapter<char>(ss), ' ');
|
json::serializer s(nlohmann::detail::output_adapter<char>(ss), ' ');
|
||||||
s.dump_escaped(original, ensure_ascii);
|
s.dump_escaped(original, ensure_ascii);
|
||||||
|
s.flush(); // dump_escaped writes into the serializer's internal buffer
|
||||||
CHECK(ss.str() == escaped);
|
CHECK(ss.str() == escaped);
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -382,3 +382,91 @@ TEST_CASE("dump for basic_json with long double number_float_t")
|
|||||||
check_same(100.0L, 100.0);
|
check_same(100.0L, 100.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("serialization of strings (bulk fast path)")
|
||||||
|
{
|
||||||
|
// These cases exercise the SWAR bulk-copy fast path in dump_escaped and the
|
||||||
|
// internal write buffer: long runs, escapes interrupting runs, 0x7F/DEL,
|
||||||
|
// multibyte UTF-8 under both ensure_ascii settings, and payloads larger than
|
||||||
|
// the write buffer.
|
||||||
|
|
||||||
|
SECTION("long unescaped ASCII exceeds the write buffer")
|
||||||
|
{
|
||||||
|
const std::string big(3000, 'a');
|
||||||
|
const json j = big;
|
||||||
|
CHECK(j.dump() == '"' + big + '"');
|
||||||
|
CHECK(j.dump(-1, ' ', true) == '"' + big + '"');
|
||||||
|
// round-trips
|
||||||
|
CHECK(json::parse(j.dump()) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("runs interrupted by escapes")
|
||||||
|
{
|
||||||
|
const json j = std::string(500, 'x') + "\n\"\\" + std::string(500, 'y');
|
||||||
|
const std::string out = j.dump();
|
||||||
|
CHECK(out == '"' + std::string(500, 'x') + "\\n\\\"\\\\" + std::string(500, 'y') + '"');
|
||||||
|
CHECK(json::parse(out) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("DEL (0x7F) depends on ensure_ascii")
|
||||||
|
{
|
||||||
|
const json j = std::string("a\x7f" "b");
|
||||||
|
CHECK(j.dump(-1, ' ', false) == "\"a\x7f" "b\""); // copied verbatim
|
||||||
|
CHECK(j.dump(-1, ' ', true) == "\"a\\u007fb\""); // escaped
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("multibyte UTF-8 under both ensure_ascii settings")
|
||||||
|
{
|
||||||
|
const json j = std::string("A\xc3\xa9\xe4\xbd\xa0\xf0\x9f\x98\x80Z"); // A é 你 😀 Z
|
||||||
|
// not escaping non-ASCII: bytes are copied through the bulk validator
|
||||||
|
CHECK(j.dump(-1, ' ', false) == "\"A\xc3\xa9\xe4\xbd\xa0\xf0\x9f\x98\x80Z\"");
|
||||||
|
// ensure_ascii: escaped (with a surrogate pair for the emoji)
|
||||||
|
CHECK(j.dump(-1, ' ', true) == "\"A\\u00e9\\u4f60\\ud83d\\ude00Z\"");
|
||||||
|
CHECK(json::parse(j.dump(-1, ' ', true)) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("many small structural writes exceed the write buffer")
|
||||||
|
{
|
||||||
|
json arr = json::array();
|
||||||
|
for (int i = 0; i < 2000; ++i)
|
||||||
|
{
|
||||||
|
arr.push_back(i);
|
||||||
|
}
|
||||||
|
const std::string out = arr.dump();
|
||||||
|
CHECK(out.front() == '[');
|
||||||
|
CHECK(out.back() == ']');
|
||||||
|
CHECK(json::parse(out) == arr);
|
||||||
|
|
||||||
|
json obj = json::object();
|
||||||
|
for (int i = 0; i < 500; ++i)
|
||||||
|
{
|
||||||
|
obj["key" + std::to_string(i)] = i;
|
||||||
|
}
|
||||||
|
CHECK(json::parse(obj.dump()) == obj);
|
||||||
|
CHECK(json::parse(obj.dump(2)) == obj);
|
||||||
|
|
||||||
|
// an array of many empty strings emits a long run of single-character
|
||||||
|
// writes ('"', '"', ',') at shallow nesting depth, so the write buffer
|
||||||
|
// fills and flushes mid-run without the deep recursion that would
|
||||||
|
// overflow the stack on some debug builds
|
||||||
|
json many_empty = json::array();
|
||||||
|
for (int i = 0; i < 500; ++i)
|
||||||
|
{
|
||||||
|
many_empty.push_back("");
|
||||||
|
}
|
||||||
|
const std::string out2 = many_empty.dump();
|
||||||
|
CHECK(out2.size() > 1024); // spans multiple write-buffer flushes
|
||||||
|
CHECK(out2.front() == '[');
|
||||||
|
CHECK(out2.back() == ']');
|
||||||
|
CHECK(json::parse(out2) == many_empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("invalid UTF-8 handling is unaffected by the fast path")
|
||||||
|
{
|
||||||
|
const json j = std::string("valid\xff" "more");
|
||||||
|
CHECK_THROWS_WITH_AS(j.dump(), "[json.exception.type_error.316] invalid UTF-8 byte at index 5: 0xFF", json::type_error&);
|
||||||
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::replace) == "\"valid\xef\xbf\xbd" "more\"");
|
||||||
|
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"valid\\ufffdmore\"");
|
||||||
|
CHECK(j.dump(-1, ' ', false, json::error_handler_t::ignore) == "\"validmore\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1862,31 +1862,6 @@ TEST_CASE("UBJSON")
|
|||||||
json _;
|
json _;
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&);
|
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("negative length")
|
|
||||||
{
|
|
||||||
json _;
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vi = {'S', 'i', 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_ubjson(vi, true, false).is_discarded());
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vI = {'S', 'I', 0xFF, 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vI), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_ubjson(vI, true, false).is_discarded());
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_ubjson(vl, true, false).is_discarded());
|
|
||||||
|
|
||||||
std::vector<uint8_t> const vL = {'S', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
|
||||||
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vL), "[json.exception.parse_error.113] parse error at byte 10: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&);
|
|
||||||
CHECK(json::from_ubjson(vL, true, false).is_discarded());
|
|
||||||
|
|
||||||
// a length of zero remains valid and yields an empty string
|
|
||||||
std::vector<uint8_t> const v0 = {'S', 'i', 0};
|
|
||||||
CHECK(json::from_ubjson(v0) == json(""));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("array")
|
SECTION("array")
|
||||||
|
|||||||
Reference in New Issue
Block a user