Compare commits

..
Author SHA1 Message Date
Niels Lohmann bad2b22115 Merge remote-tracking branch 'origin/develop' into claude/todo-190-plan-e350df
# Conflicts:
#	include/nlohmann/detail/conversions/to_json.hpp
#	single_include/nlohmann/json.hpp

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 11:15:28 +02:00
Niels Lohmann a2be332fef Fix CI: missing include and const-correctness
- Add #include <vector> to type_traits.hpp for the new
  is_compatible_binary_type trait's std::vector<std::uint8_t> reference
  (caught by cpplint's include-what-you-use check)
- Mark test-local json variables const where never reassigned
  (caught by clang-tidy's misc-const-correctness check)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 22:31:03 +02:00
Niels Lohmann c37d96115d Fix discussion #4209: custom BinaryType direct assignment and extraction
When a custom BinaryType is configured (other than the default std::vector<uint8_t>),
users can now:
1. Assign values of that type directly to create binary values (not arrays)
2. Extract binary values back to that type with get<>()
3. Extract arrays to that type (for backward compatibility)

Implementation:
- Add is_compatible_binary_type trait to centralize SFINAE condition
- Update to_json to accept custom BinaryType values directly
- Update from_json to handle both binary and array inputs for custom BinaryType
- Add #include <vector> with IWYU comment to from_json.hpp
- Add comprehensive tests for assignment and array extraction
- Update binary_t documentation with example

This is purely additive and invisible to the default nlohmann::json alias, which
continues to treat std::vector<uint8_t> as arrays.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 19:46:07 +02:00
85 changed files with 711 additions and 6829 deletions
-8
View File
@@ -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
+4 -4
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
@@ -34,19 +34,19 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
+5 -5
View File
@@ -27,25 +27,25 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
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@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
@@ -19,7 +19,7 @@ jobs:
pull-requests: write pull-requests: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
+2 -2
View File
@@ -17,12 +17,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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'
+3 -3
View File
@@ -27,12 +27,12 @@ jobs:
security-events: write security-events: write
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
+3 -3
View File
@@ -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
+2 -2
View File
@@ -27,11 +27,11 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
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
+4 -4
View File
@@ -36,17 +36,17 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif
@@ -76,6 +76,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: results.sarif sarif_file: results.sarif
+3 -3
View File
@@ -32,12 +32,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
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@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with: with:
sarif_file: semgrep.sarif sarif_file: semgrep.sarif
if: always() if: always()
+2 -2
View File
@@ -16,11 +16,11 @@ jobs:
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with: with:
stale-issue-label: 'state: stale' stale-issue-label: 'state: stale'
stale-pr-label: 'state: stale' stale-pr-label: 'state: stale'
+42 -50
View File
@@ -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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
@@ -43,11 +43,11 @@ jobs:
run: | run: |
wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ wget -q -O - "https://github.com/facebook/infer/releases/download/v1.3.0/infer-linux-x86_64-v1.3.0.tar.xz" | sudo tar -C /opt -xJ
sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer sudo ln -s /opt/infer-linux-x86_64-v1.3.0/bin/infer /usr/local/bin/infer
- uses: actions/checkout@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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -60,17 +60,17 @@ jobs:
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind] target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
- name: Install Valgrind - name: Install Valgrind
run: sudo apt-get update ; sudo apt-get install -y valgrind run: sudo apt-get update ; sudo apt-get install -y valgrind
- uses: actions/checkout@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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -118,11 +118,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
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
@@ -142,7 +142,7 @@ jobs:
name: code-coverage-report name: code-coverage-report
path: ${{ github.workspace }}/build/html path: ${{ github.workspace }}/build/html
- name: Publish report to Coveralls - name: Publish report to Coveralls
uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
@@ -163,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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -212,14 +204,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', '21', '22', '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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -369,17 +361,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with: with:
egress-policy: audit egress-policy: audit
- name: Install emscripten - name: Install emscripten
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16 uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
- uses: actions/checkout@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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -392,11 +384,11 @@ jobs:
target: [ci_test_examples, ci_test_build_documentation] target: [ci_test_examples, ci_test_build_documentation]
steps: steps:
- name: Harden Runner - name: Harden Runner
uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
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
+9 -15
View File
@@ -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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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
@@ -153,16 +153,10 @@ jobs:
with: with:
platform: x64 platform: x64
version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14 version: 12.2.0 # https://github.com/egor-tensin/setup-mingw/issues/14
# CMAKE_CXX_FLAGS_DEBUG is overridden to drop the default -g: linking
# test-regression2_cpp20 intermittently fails with "relocation truncated
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
# MinGW linker cannot relocate the debug sections this test produces.
# The tests are only built and run here, so the debug info is not used.
- name: Run CMake - name: Run CMake
run: cmake -S . -B build ^ run: cmake -S . -B build ^
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^ -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
-DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^ -DCMAKE_CXX_FLAGS="--target=x86_64-w64-mingw32 -stdlib=libstdc++ -pthread" ^
-DCMAKE_CXX_FLAGS_DEBUG="-g0" ^
-DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^ -DCMAKE_EXE_LINKER_FLAGS="-lwinpthread" ^
-G"MinGW Makefiles" ^ -G"MinGW Makefiles" ^
-DCMAKE_BUILD_TYPE=Debug ^ -DCMAKE_BUILD_TYPE=Debug ^
@@ -179,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
@@ -192,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@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2 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)
+3 -17
View File
@@ -42,7 +42,6 @@
- [Specializing enum conversion](#specializing-enum-conversion) - [Specializing enum conversion](#specializing-enum-conversion)
- [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata) - [Binary formats (BSON, CBOR, MessagePack, UBJSON, and BJData)](#binary-formats-bson-cbor-messagepack-ubjson-and-bjdata)
- [Customers](#customers) - [Customers](#customers)
- [Ecosystem](#ecosystem)
- [Supported compilers](#supported-compilers) - [Supported compilers](#supported-compilers)
- [Integration](#integration) - [Integration](#integration)
- [CMake](#cmake) - [CMake](#cmake)
@@ -91,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
@@ -1187,11 +1187,6 @@ The library is used in multiple projects, applications, operating systems, etc.
[![logos of customers using the library](docs/mkdocs/docs/images/customers.png)](https://json.nlohmann.me/home/customers/) [![logos of customers using the library](docs/mkdocs/docs/images/customers.png)](https://json.nlohmann.me/home/customers/)
## Ecosystem
Beyond projects that use the library, there are third-party projects that build on top of it - schema validators,
language bindings, format converters, and the like. See the curated [Ecosystem](https://json.nlohmann.me/community/ecosystem/) page.
## Supported compilers ## Supported compilers
Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: Though it's 2026 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work:
@@ -1807,13 +1802,13 @@ The library itself consists of a single header file licensed under the MIT licen
- [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file - [**amalgamate.py - Amalgamate C source and header files**](https://github.com/edlund/amalgamate) to create a single header file
- [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing - [**American fuzzy lop**](https://lcamtuf.coredump.cx/afl/) for fuzz testing
- [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows - [**AppVeyor**](https://www.appveyor.com) for [continuous integration](https://ci.appveyor.com/project/nlohmann/json) on Windows
- [**Artistic Style**](https://astyle.sourceforge.net) for automatic source code indentation - [**Artistic Style**](http://astyle.sourceforge.net) for automatic source code indentation
- [**Clang**](https://clang.llvm.org) for compilation with code sanitizers - [**Clang**](https://clang.llvm.org) for compilation with code sanitizers
- [**CMake**](https://cmake.org) for build automation - [**CMake**](https://cmake.org) for build automation
- [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard) - [**Codacy**](https://www.codacy.com) for further [code analysis](https://app.codacy.com/gh/nlohmann/json/dashboard)
- [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json) - [**Coveralls**](https://coveralls.io) to measure [code coverage](https://coveralls.io/github/nlohmann/json)
- [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json) - [**Coverity Scan**](https://scan.coverity.com) for [static analysis](https://scan.coverity.com/projects/nlohmann-json)
- [**cppcheck**](https://cppcheck.sourceforge.io) for static analysis - [**cppcheck**](http://cppcheck.sourceforge.net) for static analysis
- [**doctest**](https://github.com/onqtam/doctest) for the unit tests - [**doctest**](https://github.com/onqtam/doctest) for the unit tests
- [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md) - [**GitHub Changelog Generator**](https://github.com/skywinder/github-changelog-generator) to generate the [ChangeLog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md)
- [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks - [**Google Benchmark**](https://github.com/google/benchmark) to implement the benchmarks
@@ -1828,15 +1823,6 @@ The library itself consists of a single header file licensed under the MIT licen
## Notes ## Notes
### Standards compliance
The library targets strict conformance with [RFC 8259](https://tools.ietf.org/html/rfc8259.html). Both the original [JSONTestSuite](https://github.com/nst/JSONTestSuite) and its updated revision are exercised in CI; their test data is downloaded from [`nlohmann/json_test_data`](https://github.com/nlohmann/json_test_data) at configure time rather than committed to this repository (see [`tests/src/unit-testsuites.cpp`](https://github.com/nlohmann/json/blob/develop/tests/src/unit-testsuites.cpp)):
- The updated revision runs all mandatory `y_` (must-accept) and `n_` (must-reject) cases through the strict [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) entry point; the original suite runs its `n_` cases through `parse()` and its `y_` cases through [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/).
- The `i_` (implementation-defined) cases are, by RFC 8259, free to be accepted *or* rejected, so "passing all `i_` cases" is not a meaningful conformance metric. The library makes deliberate, documented choices there: nesting depth is not artificially limited, a leading UTF-8 byte order mark is silently ignored, [Unicode noncharacters](https://www.unicode.org/faq/private_use.html#nonchar1) are forwarded unchanged, invalid UTF-8 and lone/unpaired UTF-16 surrogates are rejected (stricter than required), and a number that cannot be stored without becoming `NaN`/`INF` raises [`out_of_range.406`](https://json.nlohmann.me/home/exceptions/#jsonexceptionout_of_range406).
One behavioral nuance is worth calling out, because a superficial test often misreads it as non-compliance: [`parse()`](https://json.nlohmann.me/api/basic_json/parse/) is strict and rejects trailing data after a value, whereas [`operator>>`](https://json.nlohmann.me/api/operator_gtgt/) follows relaxed iostream semantics — it parses a single value and leaves the stream positioned right after it. Feeding "a valid document followed by trailing bytes" through `operator>>` reports success; the same input through `parse()` is rejected. This is a documented two-API design, not a conformance gap. See [**parsing**](https://json.nlohmann.me/features/parsing/) for details.
### Character encoding ### Character encoding
The library supports **Unicode input** as follows: The library supports **Unicode input** as follows:
-4
View File
@@ -5,9 +5,6 @@
# -Wno-extra-semi-stmt The library uses assert which triggers this warning. # -Wno-extra-semi-stmt The library uses assert which triggers this warning.
# -Wno-padded We do not care about padding warnings. # -Wno-padded We do not care about padding warnings.
# -Wno-covered-switch-default All switches list all cases and a default case. # -Wno-covered-switch-default All switches list all cases and a default case.
# -Wno-c2y-extensions Clang 22.1 diagnoses __COUNTER__ as a C2y extension, also in
# C++ mode. The library does not use __COUNTER__; the warnings
# all come from vendored Doctest (SECTION/TEST_CASE macros).
# -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code # -Wno-unsafe-buffer-usage Pervasive: the library's own low-level numeric/buffer code
# (to_chars, serializer, lexer, binary reader/writer, input # (to_chars, serializer, lexer, binary reader/writer, input
# adapters, json_pointer) plus vendored Doctest itself (~208 # adapters, json_pointer) plus vendored Doctest itself (~208
@@ -23,6 +20,5 @@ set(CLANG_CXXFLAGS
-Wno-extra-semi-stmt -Wno-extra-semi-stmt
-Wno-padded -Wno-padded
-Wno-covered-switch-default -Wno-covered-switch-default
-Wno-c2y-extensions
-Wno-unsafe-buffer-usage -Wno-unsafe-buffer-usage
) )
+5 -13
View File
@@ -8,8 +8,8 @@ static bool accept(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static bool accept(IteratorType first, SentinelType last, static bool accept(IteratorType first, IteratorType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
``` ```
@@ -17,11 +17,10 @@ static bool accept(IteratorType first, SentinelType last,
Checks whether the input is valid JSON. Checks whether the input is valid JSON.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Reads from a pair of character iterators
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
the iterator type with `operator!=`.
Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input Unlike the [`parse()`](parse.md) function, this function neither throws an exception in case of invalid JSON input
(i.e., a parse error) nor creates diagnostic information. (i.e., a parse error) nor creates diagnostic information.
@@ -45,12 +44,6 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator` - a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
- a pair of pointers such as `ptr` and `ptr + len` - a pair of pointers such as `ptr` and `ptr + len`
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -68,7 +61,7 @@ Unlike the [`parse()`](parse.md) function, this function neither throws an excep
: iterator to the start of the character range : iterator to the start of the character range
`last` (in) `last` (in)
: iterator to the end of the character range, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the character range
## Return value ## Return value
@@ -119,7 +112,6 @@ A UTF-8 byte order mark is silently ignored.
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
- Added `ignore_trailing_commas` in version 3.13.0. - Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
+3 -2
View File
@@ -13,8 +13,9 @@ is compatible with both of the binary data formats that use binary subtyping, (t
incompatible with each other, and it is up to the user to translate between them). The subtype is added to `BinaryType` incompatible with each other, and it is up to the user to translate between them). The subtype is added to `BinaryType`
via the helper type [byte_container_with_subtype](../byte_container_with_subtype/index.md). via the helper type [byte_container_with_subtype](../byte_container_with_subtype/index.md).
[CBOR's RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) describes this type as: [CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type as:
> Major type 2: A byte string. The number of bytes in the string is equal to the argument. > Major type 2: a byte string. The string's length in bytes is represented following the rules for positive integers
> (major type 0).
[MessagePack's documentation on the bin type [MessagePack's documentation on the bin type
family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as: family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as:
-11
View File
@@ -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.
+4 -11
View File
@@ -7,8 +7,8 @@ static basic_json from_bjdata(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_bjdata(IteratorType first, SentinelType last, static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_bjdata(IteratorType first, SentinelType last,
Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format. Deserializes a given input to a JSON value using the BJData (Binary JData) serialization format.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bjdata.md).
@@ -35,12 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -50,7 +44,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `last` (in)
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`strict` (in) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -109,4 +103,3 @@ Linear in the size of the input.
- Added in version 3.11.0. - Added in version 3.11.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
+4 -11
View File
@@ -7,8 +7,8 @@ static basic_json from_bson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_bson(IteratorType first, SentinelType last, static basic_json from_bson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_bson(IteratorType first, SentinelType last,
Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format. Deserializes a given input to a JSON value using the BSON (Binary JSON) serialization format.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/bson.md).
@@ -35,12 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -50,7 +44,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `last` (in)
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`strict` (in) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -109,7 +103,6 @@ Linear in the size of the input.
- Added in version 3.4.0. - Added in version 3.4.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
+4 -11
View File
@@ -9,8 +9,8 @@ static basic_json from_cbor(InputType&& i,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_cbor(IteratorType first, SentinelType last, static basic_json from_cbor(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error); const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error);
@@ -19,7 +19,7 @@ static basic_json from_cbor(IteratorType first, SentinelType last,
Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format. Deserializes a given input to a JSON value using the CBOR (Concise Binary Object Representation) serialization format.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/cbor.md).
@@ -38,12 +38,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -53,7 +47,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `last` (in)
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`strict` (in) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -119,7 +113,6 @@ Linear in the size of the input.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- Added `tag_handler` parameter in version 3.9.0. - Added `tag_handler` parameter in version 3.9.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -7,8 +7,8 @@ static basic_json from_msgpack(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_msgpack(IteratorType first, SentinelType last, static basic_json from_msgpack(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_msgpack(IteratorType first, SentinelType last,
Deserializes a given input to a JSON value using the MessagePack serialization format. Deserializes a given input to a JSON value using the MessagePack serialization format.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/messagepack.md).
@@ -35,12 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -50,7 +44,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `last` (in)
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`strict` (in) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -111,7 +105,6 @@ Linear in the size of the input.
- Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0. - Changed to consume input adapters, removed `start_index` parameter, and added `strict` parameter in version 3.0.0.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
+4 -11
View File
@@ -7,8 +7,8 @@ static basic_json from_ubjson(InputType&& i,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json from_ubjson(IteratorType first, SentinelType last, static basic_json from_ubjson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true); const bool allow_exceptions = true);
``` ```
@@ -16,7 +16,7 @@ static basic_json from_ubjson(IteratorType first, SentinelType last,
Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format. Deserializes a given input to a JSON value using the UBJSON (Universal Binary JSON) serialization format.
1. Reads from a compatible input. 1. Reads from a compatible input.
2. Reads from an iterator range, or an iterator and a sentinel of a different type (C++20 ranges support). 2. Reads from an iterator range.
The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md). The exact mapping and its limitations are described on a [dedicated page](../../features/binary_formats/ubjson.md).
@@ -35,12 +35,6 @@ The exact mapping and its limitations are described on a [dedicated page](../../
`IteratorType` `IteratorType`
: a compatible iterator type : a compatible iterator type
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -50,7 +44,7 @@ The exact mapping and its limitations are described on a [dedicated page](../../
: iterator to the start of the input : iterator to the start of the input
`last` (in) `last` (in)
: iterator to the end of the input, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of the input
`strict` (in) `strict` (in)
: whether to expect the input to be consumed until EOF (`#!cpp true` by default) : whether to expect the input to be consumed until EOF (`#!cpp true` by default)
@@ -110,7 +104,6 @@ Linear in the size of the input.
- Added in version 3.1.0. - Added in version 3.1.0.
- Added `allow_exceptions` parameter in version 3.2.0. - Added `allow_exceptions` parameter in version 3.2.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
+5 -13
View File
@@ -10,8 +10,8 @@ static basic_json parse(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<typename IteratorType, typename SentinelType = IteratorType> template<typename IteratorType>
static basic_json parse(IteratorType first, SentinelType last, static basic_json parse(IteratorType first, IteratorType last,
const parser_callback_t cb = nullptr, const parser_callback_t cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -19,11 +19,10 @@ static basic_json parse(IteratorType first, SentinelType last,
``` ```
1. Deserialize from a compatible input. 1. Deserialize from a compatible input.
2. Deserialize from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Deserialize from a pair of character iterators
The `value_type` of the iterator must be an integral type with size of 1, 2, or 4 bytes, which will be interpreted The `value_type` of the iterator must be an integral type with size of 1, 2, or 4 bytes, which will be interpreted
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
the iterator type with `operator!=`.
## Template parameters ## Template parameters
@@ -44,12 +43,6 @@ static basic_json parse(IteratorType first, SentinelType last,
- a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator` - a pair of `std::string::iterator` or `std::vector<std::uint8_t>::iterator`
- a pair of pointers such as `ptr` and `ptr + len` - a pair of pointers such as `ptr` and `ptr + len`
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
## Parameters ## Parameters
`i` (in) `i` (in)
@@ -74,7 +67,7 @@ static basic_json parse(IteratorType first, SentinelType last,
: iterator to the start of a character range : iterator to the start of a character range
`last` (in) `last` (in)
: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of a character range
## Return value ## Return value
@@ -245,7 +238,6 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
- Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0. - Changed [runtime assertion](../../features/assertions.md) in case of `FILE*` null pointers to exception in version 3.12.0.
- Added `ignore_trailing_commas` in version 3.13.0. - Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -29,14 +29,7 @@ Discarding a value (i.e., returning `#!cpp false`) has different effects dependi
called: called:
- Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never
read. This holds for every value type and for both kinds of parent: a discarded element is removed from the read.
surrounding array, and a discarded member is removed from the surrounding object together with its key.
- Arrays and objects can be discarded either at their `parse_event_t::array_start`/`parse_event_t::object_start` event
or at their `parse_event_t::array_end`/`parse_event_t::object_end` event, and both remove the whole value. Discarding
it at the start event also means the callback is called neither for the content of the value nor for its matching end
event.
- Discarding a `parse_event_t::key` event discards the whole object member. The callback is still called for the
associated value, but its return value has no further effect.
- In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level - In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level
element is skipped. element is skipped.
@@ -56,7 +49,7 @@ called:
## Return value ## Return value
Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In Whether the JSON value which called the function during parsing should be kept (`#!cpp true`) or not (`#!cpp false`). In
the latter case, it is skipped completely, or replaced by `null` if it is the top-level value. the latter case, it is either skipped completely or replaced by an empty discarded object.
## Examples ## Examples
@@ -75,21 +68,6 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to
--8<-- "examples/parse__string__parser_callback_t.output" --8<-- "examples/parse__string__parser_callback_t.output"
``` ```
??? example
The example below shows where discarded values are removed. The array and the number are discarded in different
ways, but in each case the parse result contains neither the value nor its key.
```cpp
--8<-- "examples/parser_callback_t.cpp"
```
Output:
```json
--8<-- "examples/parser_callback_t.output"
```
## See also ## See also
- [parse](parse.md) deserialize from a compatible input - [parse](parse.md) deserialize from a compatible input
@@ -98,5 +76,3 @@ the latter case, it is skipped completely, or replaced by `null` if it is the to
## Version history ## Version history
- Added in version 1.0.0. - Added in version 1.0.0.
- Fixed in version 3.13.0 to also remove discarded values from a parent object; before, discarding an array or a value
stored under an object key left a discarded member behind, which made the parse result serialize to invalid JSON.
+5 -13
View File
@@ -11,8 +11,8 @@ static bool sax_parse(InputType&& i,
const bool ignore_trailing_commas = false); const bool ignore_trailing_commas = false);
// (2) // (2)
template<class IteratorType, class SAX, class SentinelType = IteratorType> template<class IteratorType, class SAX>
static bool sax_parse(IteratorType first, SentinelType last, static bool sax_parse(IteratorType first, IteratorType last,
SAX* sax, SAX* sax,
input_format_t format = input_format_t::json, input_format_t format = input_format_t::json,
const bool strict = true, const bool strict = true,
@@ -23,11 +23,10 @@ static bool sax_parse(IteratorType first, SentinelType last,
Read from input and generate SAX events Read from input and generate SAX events
1. Read from a compatible input. 1. Read from a compatible input.
2. Read from a pair of character iterators, or an iterator and a sentinel of a different type (C++20 ranges support) 2. Read from a pair of character iterators
The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted The value_type of the iterator must be an integral type with a size of 1, 2, or 4 bytes, which will be interpreted
respectively as UTF-8, UTF-16, and UTF-32. If `SentinelType` differs from `IteratorType`, it must be comparable to respectively as UTF-8, UTF-16, and UTF-32.
the iterator type with `operator!=`.
The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md). The SAX event lister must follow the interface of [`json_sax`](../json_sax/index.md).
@@ -47,12 +46,6 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
: a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type : a compatible iterator type for overload (2); a pair of character iterators whose `value_type` is an integral type
with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32) with a size of 1, 2, or 4 bytes (interpreted respectively as UTF-8, UTF-16, and UTF-32)
`SentinelType`
: defaults to `IteratorType`; may be a different type comparable to `IteratorType` via `operator!=`, for overload (2), for instance.
- a custom sentinel type for C++20 ranges
- `std::default_sentinel_t`, when `IteratorType` is `std::counted_iterator`
`SAX` `SAX`
: a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md) : a class fulfilling the SAX event listener interface; see [`json_sax`](../json_sax/index.md)
@@ -83,7 +76,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
: iterator to the start of a character range : iterator to the start of a character range
`last` (in) `last` (in)
: iterator to the end of a character range, or a sentinel value that compares equal to the end iterator with `operator!=` : iterator to the end of a character range
## Return value ## Return value
@@ -135,7 +128,6 @@ A UTF-8 byte order mark is silently ignored.
- Ignoring comments via `ignore_comments` added in version 3.9.0. - Ignoring comments via `ignore_comments` added in version 3.9.0.
- Added `ignore_trailing_commas` in version 3.13.0. - Added `ignore_trailing_commas` in version 3.13.0.
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0. - Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
!!! warning "Deprecation" !!! warning "Deprecation"
@@ -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
-1
View File
@@ -24,7 +24,6 @@ header. See also the [macro overview page](../../features/macros.md).
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers - [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace - [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
## Library version ## Library version
@@ -38,8 +38,7 @@ When the macro is not defined, the library will define it to its default value.
Diagnostic messages can also be controlled with the CMake option Diagnostic messages can also be controlled with the CMake option
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default) [`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
which defines `JSON_DIAGNOSTICS` accordingly. Note this only applies when building the which defines `JSON_DIAGNOSTICS` accordingly.
library from source — see the pre-installed-package caveat on that page.
## Examples ## Examples
@@ -1,59 +0,0 @@
# 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.
!!! warning "Define consistently"
The macro selects between two definitions of the same inline validation function. It must therefore be defined
identically for **every** translation unit that includes the library; mixing translation units that define it with
ones that do not is an ODR violation. Prefer setting it as a compile definition on the target rather than with
`#!cpp #define` in individual source files.
## Default definition
By default, `#!cpp JSON_USE_SIMDUTF` is not defined and the portable C++11 scalar validator is used.
```cpp
#undef JSON_USE_SIMDUTF
```
## Examples
??? example
The code below enables the simdutf backend for UTF-8 validation.
```cpp
#define JSON_USE_SIMDUTF 1
#include <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.
+5 -32
View File
@@ -33,44 +33,17 @@ A UTF-8 byte order mark is silently ignored.
Invalid Unicode escapes and unpaired surrogates in the input are reported as Invalid Unicode escapes and unpaired surrogates in the input are reported as
[`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message. [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
`operator>>` parses exactly one JSON value, so it can be called repeatedly to read a sequence of concatenated JSON `operator>>` parses exactly one JSON value and leaves the stream positioned right after it, so it can be called
values from the same stream: repeatedly to read a sequence of concatenated JSON values from the same stream:
```cpp ```cpp
json j1, j2; json j1, j2;
input >> j1; // parses the first value input >> j1; // parses the first value, stream now positioned right after it
input >> j2; // parses the next value input >> j2; // parses the next value
``` ```
!!! warning "A number must be followed by whitespace" Note this does **not** work for [JSON Lines](../features/parsing/json_lines.md) (newline-delimited JSON) input --
see that page for why and for the recommended alternative.
A number is only terminated by the character that follows it. That character is read from the stream to detect the
end of the number, and it is **not** put back. When a value that is a number is immediately followed by the next
value, the first character of that next value is lost:
```cpp
std::istringstream input("1true");
json j1, j2;
input >> j1; // j1 == 1
input >> j2; // throws parse_error.101: the stream now starts at "rue"
```
Separating the values with whitespace avoids this, because the character that is eaten is then the separator:
```cpp
std::istringstream input("1 true");
json j1, j2;
input >> j1; // j1 == 1
input >> j2; // j2 == true
```
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
`truefalse`, `[1][2]`, `{"a":1}{"b":2}`, and `"a""b"` can be read back to back without a separator.
This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
!!! warning "Deprecation" !!! warning "Deprecation"
-6
View File
@@ -13,12 +13,6 @@ Therefore, adding object elements can yield a reallocation in which case all ite
[`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or [`end()`](basic_json/end.md) iterator) and all references to the elements are invalidated. Also, any iterator or
reference after the insertion point will point to the same index, which is now a different value. reference after the insertion point will point to the same index, which is now a different value.
## Complexity
[`ordered_map`](ordered_map.md) has no lookup index: every key-based object operation is a linear scan, so building or
parsing an object of `n` keys costs O(n²) rather than O(n log n). See
[`ordered_map` complexity](ordered_map.md#complexity) for the per-operation table and for measured numbers.
## Examples ## Examples
??? example ??? example
-42
View File
@@ -56,48 +56,6 @@ std::equal_to<> // since C++14
- **find** - **find**
- **insert** - **insert**
## Complexity
Because the elements are stored in a `std::vector` in insertion order, there is no index to look a key up by. Every
key-based operation performs a **linear scan** over the stored elements. With `n` denoting the number of elements in the
container:
| Operation | Complexity | Note |
|----------------------------------------|----------------|----------------------------------------------------------|
| **emplace** | O(n) | scans for an existing key, then appends (amortized O(1)) |
| **operator\[\]** | O(n) | delegates to **emplace** (non-const) or **at** (const) |
| **at** | O(n) | throws `#!cpp std::out_of_range` if the key is not found |
| **find** | O(n) | |
| **count** | O(n) | the result is always 0 or 1 |
| **erase(key)** | O(n) | scan, then move the remaining elements one position down |
| **erase(pos)**, **erase(first, last)** | O(n) | moves all elements after the erased range |
| **insert(value)** | O(n) | equivalent to **emplace** |
| **insert(first, last)** | O((n + m) * m) | for `m` inserted elements |
This differs from `#!cpp std::map`, where the same operations are O(log n).
!!! warning "Quadratic cost of building large objects"
Because every insertion scans all elements inserted so far, building an object of `n` distinct keys costs
**O(n²)** in total. This applies to filling an [`ordered_json`](ordered_json.md) object key by key as well as to
parsing one, since the parser inserts each key as it is read.
The cost is negligible for the object sizes typically found in configuration files or API payloads, but it grows
steeply for machine-generated objects with many thousands of keys. Measured with `-O2 -DNDEBUG` for parsing a flat
object of `n` keys, relative to `#!cpp nlohmann::json` (which uses `#!cpp std::map`):
| `n` | `json` | `ordered_json` | factor |
|--------|--------|----------------|--------|
| 2000 | 0.7 ms | 3.6 ms | 5× |
| 4000 | 0.8 ms | 14.0 ms | 19× |
| 8000 | 1.6 ms | 67.8 ms | 43× |
| 16 000 | 3.3 ms | 181.6 ms | 54× |
If key order matters for objects of that size, consider a container with a lookup index, such as
[`tsl::ordered_map`](https://github.com/Tessil/ordered-map)
([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)), as the object type -- see
[object order](../features/object_order.md).
## Examples ## Examples
??? example ??? example
-40
View File
@@ -1,40 +0,0 @@
# Ecosystem
The projects below build on top of `nlohmann::json` rather than merely using it - schema validators, language
bindings, format converters, and similar building blocks. The list is not exhaustive, and is curated rather than
automatically generated. If you maintain or know of a project that belongs here,
[please let me know](mailto:mail@nlohmann.me).
For products, applications, and organizations that use the library, see [Customers](../home/customers.md) instead.
## Schema validation
- [**json-schema-validator**](https://github.com/pboettch/json-schema-validator), a JSON Schema (draft 7) validator
with human-readable error messages
## Serialization and reflection
- [**nlohmann_json_reflect**](https://github.com/1261385937/nlohmann_json_reflect), a reflection extension for
(de)serializing nested containers-in-structs-in-containers
## Encodings
- [**base-encode-decode**](https://github.com/saxonnicholls/base-encode-decode), a header-only Base64/32/16/8/4/2
(and DNA/RNA) encoding library, with an adapter that serializes binary data through `nlohmann::json`
## Language bindings and interop
- [**pybind11_json**](https://github.com/pybind/pybind11_json), a bidirectional type caster between
`nlohmann::json` and Python objects for [pybind11](https://github.com/pybind/pybind11) bindings
- [**nanobind_json**](https://github.com/ianhbell/nanobind_json), the same idea for
[nanobind](https://github.com/wjakob/nanobind) bindings
- [**nlohmann_json_qt**](https://github.com/dpurgin/nlohmann_json_qt), deserialization helpers for Qt types
(`QString`, `QUrl`, `QDateTime`, `QVector`, ...) from `nlohmann::json`
- [**vulkan2json**](https://github.com/Fadis/vulkan2json), serialization and deserialization of Vulkan API structs
## Format converters
- [**tojson**](https://github.com/mircodz/tojson), a header-only converter between YAML/XML documents and
`nlohmann::json`
- [**json2xml**](https://github.com/testillano/json2xml), a header-only converter from `nlohmann::json` to XML for
simple configuration documents
-1
View File
@@ -1,6 +1,5 @@
# Community # Community
- [Ecosystem](ecosystem.md) - third-party projects built on top of this library
- [Code of Conduct](code_of_conduct.md) - the rules and norms of this project - [Code of Conduct](code_of_conduct.md) - the rules and norms of this project
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project - [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
- [Governance](governance.md) - the governance model of this project - [Governance](governance.md) - the governance model of this project
@@ -66,7 +66,6 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
| Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | | Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub | | Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub |
| Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | | Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| Clang 22.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub |
| CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | | CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
| CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | | CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
| CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | | CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub |
@@ -1,47 +0,0 @@
#include <iostream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main()
{
// a JSON text with an array and a number inside an object
auto text = R"({"IDs": [116, 943], "Width": 800})";
// discard the array when the parser reads its opening bracket
json j_array_start = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
{
return event != json::parse_event_t::array_start;
});
// discard the same array when the parser reads its closing bracket
json j_array_end = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
{
return event != json::parse_event_t::array_end;
});
// discard the number, but keep its key
json j_value = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
{
return !(event == json::parse_event_t::value && parsed == json(800));
});
// discard the key of the number
json j_key = json::parse(text, [](int /*depth*/, json::parse_event_t event, json & parsed)
{
return !(event == json::parse_event_t::key && parsed == json("Width"));
});
// discard the top-level object
json j_root = json::parse(text, [](int /*depth*/, json::parse_event_t event, json& /*parsed*/)
{
return event != json::parse_event_t::object_end;
});
// in every case, the discarded value is removed together with its key
std::cout << j_array_start << '\n'
<< j_array_end << '\n'
<< j_value << '\n'
<< j_key << '\n'
<< j_root << '\n';
}
@@ -1,5 +0,0 @@
{"Width":800}
{"Width":800}
{"IDs":[116,943]}
{"IDs":[116,943]}
null
@@ -116,19 +116,9 @@ The library uses the following mapping from JSON values types to BJData types ac
``` ```
Likewise, when a JSON object in the above form is serialized using Likewise, when a JSON object in the above form is serialized using
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When [`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. The
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a only exception is, that when the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two
regular 1-D optimized array is generated instead. integers with one being 1, a regular 1-D optimized array is generated.
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
regular JSON object. This requires all of the following:
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
`double`, `char`, or `byte`,
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
- `"_ArrayData_"` holds exactly that many elements, and
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
`single` and `double`, an integer otherwise).
The current version of this library does not yet support automatic detection of and conversion from a nested JSON The current version of this library does not yet support automatic detection of and conversion from a nested JSON
array input to a BJData ND-array. array input to a BJData ND-array.
@@ -35,19 +35,6 @@ The library uses the following mapping from JSON values types to BSON types:
The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON. The mapping is **incomplete**, since only JSON-objects (and things contained therein) can be serialized to BSON.
Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings. Also, keys may not contain U+0000, since they are serialized a zero-terminated c-strings.
!!! warning "BSON type 0x11 interoperability"
The BSON specification defines type `0x11` as a Timestamp. This library uses marker `0x11` when serializing
`number_unsigned` values in the range `9223372036854775808..18446744073709551615`. Other BSON implementations may
therefore interpret these values as Timestamps instead of unsigned integers.
!!! info "Binary values without a subtype"
BSON requires every binary value to have a subtype. If a binary value has no subtype, this library serializes it
with the generic subtype `0x00`. After deserialization, `has_subtype()` returns `true` and `subtype()` returns `0`.
As a result, serializing and deserializing a JSON object containing such a value produces a different JSON object,
even though the binary data is unchanged.
??? example ??? example
```cpp ```cpp
@@ -95,8 +82,8 @@ The library maps BSON record types to JSON value types as follows:
!!! note "Handling of BSON type 0x11" !!! note "Handling of BSON type 0x11"
This library deserializes BSON type `0x11` (Timestamp) as a `number_unsigned` value. The 64-bit value is preserved, BSON type 0x11 is used to represent uint64 numbers. This library treats these values purely as uint64 numbers
but the Timestamp type information is not. and does not parse them into date-related formats.
??? example ??? example
@@ -7,12 +7,12 @@ extremely small code sizes, fairly small message size, and extensibility without
- [CBOR Website](http://cbor.io) - the main source on CBOR - [CBOR Website](http://cbor.io) - the main source on CBOR
- [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR - [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR
- [RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html) - the CBOR specification - [RFC 7049](https://tools.ietf.org/html/rfc7049) - the CBOR specification
## Serialization ## Serialization
The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification The library uses the following mapping from JSON values types to CBOR types according to the CBOR specification
([RFC 8949](https://www.rfc-editor.org/rfc/rfc8949.html)): ([RFC 7049](https://www.rfc-editor.org/rfc/rfc7049.html)):
| JSON value type | value/range | CBOR type | first byte | | JSON value type | value/range | CBOR type | first byte |
|-----------------|--------------------------------------------|-----------------------------------|------------| |-----------------|--------------------------------------------|-----------------------------------|------------|
+5 -64
View File
@@ -47,34 +47,6 @@ json j = {{"one", 1}, {"two", 2}};
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}} auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
``` ```
`#!cpp std::pair` and `#!cpp std::tuple` are also supported, converting positionally to and from a JSON array:
```cpp
json j = {1.0, "hello", 42};
auto t = j.get<std::tuple<double, std::string, int>>(); // {1.0, "hello", 42}
```
!!! info "Extracting references into a tuple"
A tuple type may also hold references (e.g. `#!cpp std::tuple<double&, std::string&>`) to avoid copying: `get`
then returns a tuple of references pointing directly at the elements stored inside the `basic_json` array,
rather than a tuple of copies:
```cpp
json j = {1.0, "hello"};
auto refs = j.get<std::tuple<double&, std::string&>>();
std::get<1>(refs) = "world"; // modifies j[1] in place
```
A referenced element must name the type the library actually *stores* — one of [`boolean_t`](../api/basic_json/boolean_t.md),
[`number_integer_t`](../api/basic_json/number_integer_t.md), [`number_unsigned_t`](../api/basic_json/number_unsigned_t.md),
[`number_float_t`](../api/basic_json/number_float_t.md), [`string_t`](../api/basic_json/string_t.md),
[`binary_t`](../api/basic_json/binary_t.md), [`array_t`](../api/basic_json/array_t.md), or
[`object_t`](../api/basic_json/object_t.md). There is nothing else to refer to, so a reference to any other type is a
compile error even when a conversion would exist: `#!cpp std::tuple<int&>` is rejected, because the library stores a
`#!cpp number_integer_t` (`#!cpp std::int64_t` by default) and not an `#!cpp int`. This restriction applies only to
reference elements — a plain `#!cpp std::tuple<int>` converts by value as usual.
## Implicit conversions ## Implicit conversions
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted: By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
@@ -122,34 +94,17 @@ which forces the explicit `get` form and can catch unintended conversions at com
with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()` with a custom `adl_serializer<std::optional<T>>` specialization. Prefer `get<std::optional<T>>()`/`get_to()`
over `static_cast` for optional types. over `static_cast` for optional types.
!!! warning "Converting to a fixed-size destination does not check the array size" !!! warning "Converting to a fixed-size `std::array` does not check length"
Some destination types have a size that is fixed by their C++ type rather than by the JSON value: Converting a JSON array to `#!cpp std::array<T, N>` does not check that the JSON array's size matches `N`:
`#!cpp std::pair<A, B>`, `#!cpp std::tuple<Ts...>`, `#!cpp std::array<T, N>`, C arrays `#!cpp T[N]`, and if the JSON array is longer, the extra elements are silently dropped; if it is shorter, the remaining
`#!cpp std::map`/`#!cpp std::unordered_map` with a non-string key type (which is read from an array of `std::array` elements are left default-constructed. No exception is thrown in either case.
two-element arrays). All of them read exactly as many elements as they need via
[`at`](../api/basic_json/at.md) and **never compare the JSON array's size to that number**. The two
mismatch directions therefore behave differently:
- The JSON array has **too many** elements: the surplus is **silently discarded**, and no exception is
thrown.
- The JSON array has **too few** elements: `at` throws
[`out_of_range.401`](../home/exceptions.md#jsonexceptionout_of_range401) for the first missing index --
an out-of-range error, not a [`type_error`](../home/exceptions.md#type-errors), even though the cause
is a shape mismatch.
```cpp ```cpp
json j = {1, 2, 3, 4, 5}; json j = {1, 2, 3, 4, 5};
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
auto p = j.get<std::pair<int, int>>(); // (1, 2) -- elements 3, 4, and 5 silently dropped
json k = {1};
auto q = k.get<std::pair<int, int>>(); // ❌ throws out_of_range.401
``` ```
If a size mismatch is an error in your application, check the size yourself before converting.
## Omitting a field when serializing `std::optional` ## Omitting a field when serializing `std::optional`
By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way By default, `to_json` for `std::optional<T>` writes either the value or `#!json null` -- there is no built-in way
@@ -181,20 +136,6 @@ std::vector<int> numbers = {1, 2, 3};
json j = numbers; // [1,2,3] json j = numbers; // [1,2,3]
``` ```
!!! info "Constructing from a C++20 range view"
A `json` array can also be constructed directly from a C++20 range view (`std::ranges::view`), such as the result
of `std::views::filter` or `std::views::transform` -- no intermediate container is needed:
```cpp
std::vector<int> nums{1, 2, 37, 42, 21};
auto filtered = nums | std::views::filter([](int i) { return i > 10; });
json j(filtered); // [37,42,21]
```
This requires [`JSON_HAS_RANGES`](../api/macros/json_has_ranges.md) to be enabled and is unavailable on MinGW due
to incomplete C++20 ranges support there.
## Your own types ## Your own types
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
-8
View File
@@ -137,14 +137,6 @@ behavior is deprecated and switched off (`0`) by default.
See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md). See [full documentation of `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md).
## `JSON_USE_SIMDUTF`
When defined, UTF-8 validation of JSON strings read from contiguous byte input is delegated to the
[simdutf](https://github.com/simdutf/simdutf) library instead of the built-in scalar validator. This is an opt-in
external dependency and is not defined by default.
See [full documentation of `JSON_USE_SIMDUTF`](../api/macros/json_use_simdutf.md).
## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)` ## `NLOHMANN_DEFINE_TYPE_*(...)`, `NLOHMANN_DEFINE_DERIVED_TYPE_*(...)`
The library defines 12 macros to simplify the serialization/deserialization of types. See the page on The library defines 12 macros to simplify the serialization/deserialization of types. See the page on
@@ -53,12 +53,6 @@ If you do want to preserve the **insertion order**, you can use the type [`nlohm
Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)). Alternatively, you can use a more sophisticated ordered map like [`tsl::ordered_map`](https://github.com/Tessil/ordered-map) ([integration](https://github.com/nlohmann/json/issues/546#issuecomment-304447518)) or [`nlohmann::fifo_map`](https://github.com/nlohmann/fifo_map) ([integration](https://github.com/nlohmann/json/issues/485#issuecomment-333652309)).
The [`ordered_map`](../api/ordered_map.md) behind `nlohmann::ordered_json` is deliberately minimal and has no lookup
index, so every key access is a linear scan and building an object of `n` keys costs O(n²). This is unnoticeable at
typical object sizes but becomes significant for objects with many thousands of keys; see
[`ordered_map` complexity](../api/ordered_map.md#complexity). The alternatives above keep a lookup index and do not
have this cost.
### Notes on parsing ### Notes on parsing
Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file. Note that you also need to call the right [`parse`](../api/basic_json/parse.md) function when reading from a file.
@@ -28,22 +28,6 @@ Inputs consisting of multiple values separated by newlines are handled by the [J
By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse` By default, the library rejects comments and trailing commas. Both can be enabled with parameters of the `parse`
function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md). function — see [comments](../comments.md) and [trailing commas](../trailing_commas.md).
## Strictness and trailing data
[`parse`](../../api/basic_json/parse.md) reads a single JSON value and requires the whole input to be consumed: any
non-whitespace data after the value is reported as a parse error. Use it when you want to guarantee that an input is
exactly one complete JSON document.
[`operator>>`](../../api/operator_gtgt.md) follows relaxed `#!cpp std::istream` semantics instead: it parses one JSON
value and leaves the stream positioned right after it, without requiring the rest of the stream to be consumed. This is
what makes it possible to read several concatenated values from the same stream, but it also means that "a valid
document followed by trailing bytes" is accepted rather than rejected. If you are validating conformance, or need to
reject any input that is not exactly one JSON document, prefer `parse`.
When using `operator>>` to read several concatenated values this way, a value that is a number must be followed by
whitespace, because `operator>>` consumes the character that terminates a number — see the
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
## SAX vs. DOM parsing ## SAX vs. DOM parsing
The library offers two parsing models: The library offers two parsing models:
@@ -49,5 +49,4 @@ JSON Lines input with more than one value is treated as invalid JSON by the [`pa
with a JSON Lines input does not work, because the parser will try to parse one value after the last one. with a JSON Lines input does not work, because the parser will try to parse one value after the last one.
This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which This is different from parsing a stream of *concatenated* (non-newline-delimited) JSON values, for which
`operator>>` does work, provided that a value that is a number is followed by whitespace -- see its `operator>>` does work -- see its [notes](../../api/operator_gtgt.md#notes) for details.
[notes](../../api/operator_gtgt.md#notes) for details.
+5 -40
View File
@@ -291,10 +291,9 @@ A JSON Pointer array index must be a number.
### json.exception.parse_error.110 ### json.exception.parse_error.110
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read.
been read.
!!! failure "Example messages" !!! failure "Example message"
``` ```
[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input [json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR string: unexpected end of input
@@ -302,9 +301,6 @@ been read.
``` ```
[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A [json.exception.parse_error.110] parse error at byte 2: syntax error while parsing UBJSON value: expected end of input; last byte: 0x5A
``` ```
```
[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input
```
### json.exception.parse_error.112 ### json.exception.parse_error.112
@@ -333,14 +329,10 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
``` ```
[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow [json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow
``` ```
```
[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
```
### json.exception.parse_error.113 ### json.exception.parse_error.113
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a While parsing a map key, a value that is not a string has been read.
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
!!! failure "Example messages" !!! failure "Example messages"
@@ -353,9 +345,6 @@ string was read where one was required (for instance as a map key), or the strin
``` ```
[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82 [json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON char: byte after 'C' must be in range 0x00..0x7F; last byte: 0x82
``` ```
```
[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative
```
### json.exception.parse_error.114 ### json.exception.parse_error.114
@@ -864,21 +853,13 @@ and this exception no longer occurs.
### json.exception.out_of_range.408 ### json.exception.out_of_range.408
The size of an array or object in a [binary format](../features/binary_formats/index.md) exceeds the maximal capacity: The size (following `#`) of an UBJSON array or object exceeds the maximal capacity.
the size following `#` for [UBJSON](../features/binary_formats/ubjson.md)/[BJData](../features/binary_formats/bjdata.md),
or the encoded length for [CBOR](../features/binary_formats/cbor.md).
!!! failure "Example messages" !!! failure "Example message"
``` ```
excessive array size: 8658170730974374167 excessive array size: 8658170730974374167
``` ```
```
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size
```
```
[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size
```
### json.exception.out_of_range.409 ### json.exception.out_of_range.409
@@ -917,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
-21
View File
@@ -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
+1
View File
@@ -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!
-25
View File
@@ -135,31 +135,6 @@ Enable CI build targets. The exact targets are used during the several CI steps
Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default. Enable [extended diagnostic messages](../home/exceptions.md#extended-diagnostic-messages) by defining macro [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md). This option is `OFF` by default.
!!! warning "Does not apply to a pre-installed package"
This option only takes effect when building nlohmann/json from source as part of your own
CMake project (e.g. via [`FetchContent`](#fetchcontent) or [`add_subdirectory`](#external)).
It has **no effect** on a package that was already built and installed elsewhere (Homebrew,
vcpkg, a system package, etc.) — the resulting compile definition is baked into the exported
`nlohmann_jsonTargets.cmake` at install time, and `set(JSON_Diagnostics ON)` before
`find_package()` does not change it (verified against the Homebrew-installed package: the
exported target still carries a fixed `$<$<BOOL:OFF>:JSON_DIAGNOSTICS=1>`, regardless of any
variable set in the consuming project).
To enable extended diagnostics for a pre-installed package, override the imported target's
property directly after `find_package()`:
```cmake
find_package(nlohmann_json REQUIRED)
set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "JSON_DIAGNOSTICS=1")
```
This only works cleanly when your project is the sole consumer of that imported target. If
nlohmann_json is pulled in from more than one place in your dependency graph with different
`JSON_DIAGNOSTICS` values, you may see a `"JSON_DIAGNOSTICS" redefined` compiler error, since
conflicting `-D` flags can end up on the same compile command line.
### `JSON_Diagnostic_Positions` ### `JSON_Diagnostic_Positions`
Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default. Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md). This option is `OFF` by default.
@@ -930,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
-2
View File
@@ -296,7 +296,6 @@ 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
@@ -309,7 +308,6 @@ nav:
- 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md - 'NLOHMANN_JSON_VERSION_MAJOR, NLOHMANN_JSON_VERSION_MINOR, NLOHMANN_JSON_VERSION_PATCH': api/macros/nlohmann_json_version_major.md
- Community: - Community:
- community/index.md - community/index.md
- community/ecosystem.md
- "Code of Conduct": community/code_of_conduct.md - "Code of Conduct": community/code_of_conduct.md
- community/contribution_guidelines.md - community/contribution_guidelines.md
- community/quality_assurance.md - community/quality_assurance.md
+1 -1
View File
@@ -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"
+37 -174
View File
@@ -163,44 +163,14 @@ 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{};
if (!get_number<std::int32_t, true>(input_format_t::bson, document_size)) get_number<std::int32_t, true>(input_format_t::bson, document_size);
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{ {
@@ -212,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();
} }
@@ -290,10 +255,7 @@ class binary_reader
// All BSON binary values have a subtype // All BSON binary values have a subtype
std::uint8_t subtype{}; std::uint8_t subtype{};
if (JSON_HEDLEY_UNLIKELY(!get_number<std::uint8_t>(input_format_t::bson, subtype))) get_number<std::uint8_t>(input_format_t::bson, subtype);
{
return false;
}
result.set_subtype(subtype); result.set_subtype(subtype);
return get_binary(input_format_t::bson, len, result); return get_binary(input_format_t::bson, len, result);
@@ -346,8 +308,7 @@ class binary_reader
case 0x08: // boolean case 0x08: // boolean
{ {
std::uint8_t value{}; return sax->boolean(get() != 0);
return get_number<std::uint8_t>(input_format_t::bson, value) && sax->boolean(value != 0);
} }
case 0x0A: // null case 0x0A: // null
@@ -436,12 +397,8 @@ 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{};
if (!get_number<std::int32_t, true>(input_format_t::bson, document_size)) get_number<std::int32_t, true>(input_format_t::bson, document_size);
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{ {
@@ -453,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();
} }
@@ -465,6 +417,15 @@ class binary_reader
// CBOR // // CBOR //
////////// //////////
/*!
@param[in] get_char whether a new character should be retrieved from the
input (true) or whether the last read character should
be considered instead (false)
@param[in] tag_handler how CBOR tags should be treated
@return whether a valid CBOR value was passed to the SAX parser
*/
template<typename NumberType> template<typename NumberType>
bool get_cbor_negative_integer() bool get_cbor_negative_integer()
{ {
@@ -483,14 +444,6 @@ class binary_reader
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number)); return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
} }
/*!
@param[in] get_char whether a new character should be retrieved from the
input (true) or whether the last read character should
be considered instead (false)
@param[in] tag_handler how CBOR tags should be treated
@return whether a valid CBOR value was passed to the SAX parser
*/
bool parse_cbor_internal(const bool get_char, bool parse_cbor_internal(const bool get_char,
const cbor_tag_handler_t tag_handler) const cbor_tag_handler_t tag_handler)
{ {
@@ -703,15 +656,13 @@ class binary_reader
case 0x9A: // array (four-byte uint32_t for n follow) case 0x9A: // array (four-byte uint32_t for n follow)
{ {
std::uint32_t len{}; std::uint32_t len{};
std::size_t size{}; return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "array") && get_cbor_array(size, tag_handler);
} }
case 0x9B: // array (eight-byte uint64_t for n follow) case 0x9B: // array (eight-byte uint64_t for n follow)
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "array") && get_cbor_array(size, tag_handler);
} }
case 0x9F: // array (indefinite length) case 0x9F: // array (indefinite length)
@@ -759,15 +710,13 @@ class binary_reader
case 0xBA: // map (four-byte uint32_t for n follow) case 0xBA: // map (four-byte uint32_t for n follow)
{ {
std::uint32_t len{}; std::uint32_t len{};
std::size_t size{}; return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "map") && get_cbor_object(size, tag_handler);
} }
case 0xBB: // map (eight-byte uint64_t for n follow) case 0xBB: // map (eight-byte uint64_t for n follow)
{ {
std::uint64_t len{}; std::uint64_t len{};
std::size_t size{}; return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "map") && get_cbor_object(size, tag_handler);
} }
case 0xBF: // map (indefinite length) case 0xBF: // map (indefinite length)
@@ -810,37 +759,25 @@ class binary_reader
case 0xD8: case 0xD8:
{ {
std::uint8_t subtype_to_ignore{}; std::uint8_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore)) get_number(input_format_t::cbor, subtype_to_ignore);
{
return false;
}
break; break;
} }
case 0xD9: case 0xD9:
{ {
std::uint16_t subtype_to_ignore{}; std::uint16_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore)) get_number(input_format_t::cbor, subtype_to_ignore);
{
return false;
}
break; break;
} }
case 0xDA: case 0xDA:
{ {
std::uint32_t subtype_to_ignore{}; std::uint32_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore)) get_number(input_format_t::cbor, subtype_to_ignore);
{
return false;
}
break; break;
} }
case 0xDB: case 0xDB:
{ {
std::uint64_t subtype_to_ignore{}; std::uint64_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore)) get_number(input_format_t::cbor, subtype_to_ignore);
{
return false;
}
break; break;
} }
default: default:
@@ -858,40 +795,28 @@ class binary_reader
case 0xD8: case 0xD8:
{ {
std::uint8_t subtype{}; std::uint8_t subtype{};
if (!get_number(input_format_t::cbor, subtype)) get_number(input_format_t::cbor, subtype);
{
return false;
}
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break; break;
} }
case 0xD9: case 0xD9:
{ {
std::uint16_t subtype{}; std::uint16_t subtype{};
if (!get_number(input_format_t::cbor, subtype)) get_number(input_format_t::cbor, subtype);
{
return false;
}
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break; break;
} }
case 0xDA: case 0xDA:
{ {
std::uint32_t subtype{}; std::uint32_t subtype{};
if (!get_number(input_format_t::cbor, subtype)) get_number(input_format_t::cbor, subtype);
{
return false;
}
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break; break;
} }
case 0xDB: case 0xDB:
{ {
std::uint64_t subtype{}; std::uint64_t subtype{};
if (!get_number(input_format_t::cbor, subtype)) get_number(input_format_t::cbor, subtype);
{
return false;
}
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype)); b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break; break;
} }
@@ -933,7 +858,7 @@ class binary_reader
const auto byte1 = static_cast<unsigned char>(byte1_raw); const auto byte1 = static_cast<unsigned char>(byte1_raw);
const auto byte2 = static_cast<unsigned char>(byte2_raw); const auto byte2 = static_cast<unsigned char>(byte2_raw);
// Code from RFC 8949, Appendix D, Figure 3: // Code from RFC 7049, Appendix D, Figure 3:
// As half-precision floating-point numbers were only added // As half-precision floating-point numbers were only added
// to IEEE 754 in 2008, today's programming platforms often // to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very // still only have limited support for them. It is very
@@ -946,8 +871,8 @@ class binary_reader
{ {
const int exp = (half >> 10u) & 0x1Fu; const int exp = (half >> 10u) & 0x1Fu;
const unsigned int mant = half & 0x3FFu; const unsigned int mant = half & 0x3FFu;
JSON_ASSERT(exp <= 31); JSON_ASSERT(0 <= exp&& exp <= 32);
JSON_ASSERT(mant <= 1023); JSON_ASSERT(mant <= 1024);
switch (exp) switch (exp)
{ {
case 0: case 0:
@@ -1182,31 +1107,6 @@ class binary_reader
} }
} }
/*!
@brief narrow a definite CBOR array/map length to std::size_t
A definite length is rejected if it does not fit in std::size_t or if it
equals detail::unknown_size(), which is reserved to mark an indefinite-
length container and would otherwise make the length read as indefinite.
Both cases exceed any container's max_size(), so no representable input
is affected.
@param[in] len the declared length
@param[out] result the length narrowed to std::size_t
@param[in] context "array" or "map", for the error message
@return whether the length is usable
*/
bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context)
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::size_t>(len) || len == detail::unknown_size()))
{
return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408,
exception_message(input_format_t::cbor, concat("excessive ", context, " size"), "size"), nullptr));
}
result = conditional_static_cast<std::size_t>(len);
return true;
}
/*! /*!
@param[in] len the length of the array or detail::unknown_size() for an @param[in] len the length of the array or detail::unknown_size() for an
array of indefinite size array of indefinite size
@@ -1946,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
@@ -1987,11 +1864,7 @@ class binary_reader
{ {
if (get_char) if (get_char)
{ {
// no get_ignore_noop() here: the byte read next must be a string get(); // TODO(niels): may we ignore N here?
// length type specification, and a no-op ('N') is not valid in
// that position. No-ops at positions where a value may appear are
// already consumed by the callers via get_ignore_noop().
get();
} }
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value"))) if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, "value")))
@@ -2010,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':
@@ -2550,7 +2423,7 @@ class binary_reader
const auto byte1 = static_cast<unsigned char>(byte1_raw); const auto byte1 = static_cast<unsigned char>(byte1_raw);
const auto byte2 = static_cast<unsigned char>(byte2_raw); const auto byte2 = static_cast<unsigned char>(byte2_raw);
// Code from RFC 8949, Appendix D, Figure 3: // Code from RFC 7049, Appendix D, Figure 3:
// As half-precision floating-point numbers were only added // As half-precision floating-point numbers were only added
// to IEEE 754 in 2008, today's programming platforms often // to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very // still only have limited support for them. It is very
@@ -2563,8 +2436,8 @@ class binary_reader
{ {
const int exp = (half >> 10u) & 0x1Fu; const int exp = (half >> 10u) & 0x1Fu;
const unsigned int mant = half & 0x3FFu; const unsigned int mant = half & 0x3FFu;
JSON_ASSERT(exp <= 31); JSON_ASSERT(0 <= exp&& exp <= 32);
JSON_ASSERT(mant <= 1023); JSON_ASSERT(mant <= 1024);
switch (exp) switch (exp)
{ {
case 0: case 0:
@@ -2881,17 +2754,7 @@ class binary_reader
case token_type::value_unsigned: case token_type::value_unsigned:
return sax->number_unsigned(number_lexer.get_number_unsigned()); return sax->number_unsigned(number_lexer.get_number_unsigned());
case token_type::value_float: case token_type::value_float:
{ return sax->number_float(number_lexer.get_number_float(), std::move(number_string));
const auto parsed_float = number_lexer.get_number_float();
if (JSON_HEDLEY_UNLIKELY(!std::isfinite(parsed_float)))
{
return sax->parse_error(
chars_read,
number_string,
out_of_range::create(406, concat("number overflow parsing '", number_string, '\''), nullptr));
}
return sax->number_float(parsed_float, std::move(number_string));
}
case token_type::uninitialized: case token_type::uninitialized:
case token_type::literal_true: case token_type::literal_true:
case token_type::literal_false: case token_type::literal_false:
+32 -192
View File
@@ -155,31 +155,9 @@ class input_stream_adapter
// General-purpose iterator-based adapter. It might not be as fast as // General-purpose iterator-based adapter. It might not be as fast as
// theoretically possible for some containers, but it is extremely versatile. // theoretically possible for some containers, but it is extremely versatile.
// SentinelType defaults to IteratorType for backward compatibility, but may be template<typename IteratorType>
// a different type, e.g. a C++20 sentinel such as std::default_sentinel_t when
// IteratorType is a std::counted_iterator.
template<typename IteratorType, typename SentinelType = IteratorType>
class iterator_input_adapter class iterator_input_adapter
{ {
// Whether the number of elements between two positions can be computed in
// O(1): either the iterator and the sentinel have the same type (plain
// std::distance) or, in C++20, the sentinel is a sized sentinel for the
// iterator (std::ranges::distance), e.g. std::default_sentinel_t paired
// with std::counted_iterator.
//
// JSON_HAS_RANGES gates the C++20 branch: on standard libraries with an
// incomplete <ranges> (libstdc++ < 11, see #4440) evaluating
// std::contiguous_iterator on a std::counted_iterator is a hard error
// instead of yielding false, and these traits are instantiated for every
// adapter. Such toolchains fall back to the pointer-only test and simply
// use the byte-at-a-time scanner.
static constexpr bool sentinel_is_sized =
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::is_same<IteratorType, SentinelType>::value || std::sized_sentinel_for<SentinelType, IteratorType>;
#else
std::is_same<IteratorType, SentinelType>::value;
#endif
public: public:
using char_type = typename std::iterator_traits<IteratorType>::value_type; using char_type = typename std::iterator_traits<IteratorType>::value_type;
@@ -191,10 +169,9 @@ class iterator_input_adapter
// in wide_string_input_adapter, which does not expose this). // in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek = static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
&& sentinel_is_sized
&& sizeof(char_type) == 1; && sizeof(char_type) == 1;
iterator_input_adapter(IteratorType first, SentinelType last) iterator_input_adapter(IteratorType first, IteratorType last)
: begin(first), current(std::move(first)), end(std::move(last)) : begin(first), current(std::move(first)), end(std::move(last))
{} {}
@@ -239,60 +216,19 @@ class iterator_input_adapter
private: private:
// whether IteratorType refers to a contiguous range and therefore supports // whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect // a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string). The // library iterators such as those of std::vector and std::string)
// available element count must also be computable in O(1), hence static constexpr bool iterator_is_contiguous =
// sentinel_is_sized. #if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
static constexpr bool iterator_is_contiguous = sentinel_is_sized && std::contiguous_iterator<IteratorType> ||
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) #endif
(std::contiguous_iterator<IteratorType> || std::is_pointer<IteratorType>::value);
#else
std::is_pointer<IteratorType>::value; std::is_pointer<IteratorType>::value;
#endif
// number of unread elements in [current, end)
std::size_t remaining_count() const
{
#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// std::ranges::distance also supports sized sentinels of a different
// type (e.g. std::counted_iterator + std::default_sentinel_t)
return static_cast<std::size_t>(std::ranges::distance(current, end));
#else
return static_cast<std::size_t>(std::distance(current, end));
#endif
}
public:
// Whether the remaining input is a single contiguous block of 1-byte
// elements that the lexer can inspect directly (used for the SWAR string
// fast path).
static constexpr bool supports_bulk_scan =
iterator_is_contiguous && sizeof(char_type) == 1;
// Pointer to the next unread element; only valid when bulk_remaining() > 0.
const char_type* bulk_data() const
{
return &*current;
}
// Number of unread elements available as one contiguous block.
std::size_t bulk_remaining() const
{
return remaining_count();
}
// Consume @a n elements previously inspected via bulk_data().
void bulk_skip(std::size_t n)
{
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(n));
}
private:
// contiguous fast path: bulk copy the remaining range with std::memcpy // contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T> template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/) std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{ {
const std::size_t wanted = count * sizeof(T); const std::size_t wanted = count * sizeof(T);
const std::size_t available = remaining_count() * sizeof(char_type); const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
const std::size_t copied = (std::min)(wanted, available); const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0)) if (JSON_HEDLEY_LIKELY(copied != 0))
{ {
@@ -331,7 +267,7 @@ class iterator_input_adapter
IteratorType begin; IteratorType begin;
IteratorType current; IteratorType current;
SentinelType end; IteratorType end;
template<typename BaseInputAdapter, size_t T> template<typename BaseInputAdapter, size_t T>
friend struct wide_string_input_helper; friend struct wide_string_input_helper;
@@ -395,12 +331,8 @@ struct wide_string_input_helper<BaseInputAdapter, 4>
} }
else else
{ {
// A code point above U+10FFFF has no UTF-8 encoding. Passing the // unknown character
// unit through would narrow it to int, where 0xFFFFFFFF becomes utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
// char_traits<char>::eof() and would end the input silently, so
// emit a byte that is never valid UTF-8 and let the decoder
// reject it.
utf8_bytes[0] = 0xFF;
utf8_bytes_filled = 1; utf8_bytes_filled = 1;
} }
} }
@@ -449,30 +381,17 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
} }
else else
{ {
// A supplementary code point is a high surrogate (0xD800..0xDBFF) if (JSON_HEDLEY_UNLIKELY(!input.empty()))
// followed by a low surrogate (0xDC00..0xDFFF). A lone low
// surrogate, a high surrogate at the end of the input, or a high
// surrogate followed by any other unit is malformed UTF-16. In
// that case the offending unit is passed through unchanged so the
// UTF-8 decoder rejects it, matching how \uXXXX surrogate escapes
// are handled in the lexer.
bool valid_pair = false;
if (wc <= 0xDBFF && JSON_HEDLEY_UNLIKELY(!input.empty()))
{ {
const auto wc2 = static_cast<unsigned int>(input.get_character()); const auto wc2 = static_cast<unsigned int>(input.get_character());
if (0xDC00 <= wc2 && wc2 <= 0xDFFF) const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));
{ utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));
const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu)); utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u)); utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu)); utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu)); utf8_bytes_filled = 4;
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
valid_pair = true;
}
} }
else
if (!valid_pair)
{ {
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc); utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1; utf8_bytes_filled = 1;
@@ -534,54 +453,19 @@ class wide_string_input_adapter
std::size_t utf8_bytes_filled = 0; std::size_t utf8_bytes_filled = 0;
}; };
template<typename IteratorType, typename SentinelType = IteratorType, typename Enable = void> template<typename IteratorType, typename Enable = void>
struct iterator_input_adapter_factory struct iterator_input_adapter_factory
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; using char_type = typename std::iterator_traits<iterator_type>::value_type;
using adapter_type = iterator_input_adapter<iterator_type, sentinel_type>; using adapter_type = iterator_input_adapter<iterator_type>;
static adapter_type create(IteratorType first, SentinelType last) static adapter_type create(IteratorType first, IteratorType last)
{ {
return adapter_type(std::move(first), std::move(last)); return adapter_type(std::move(first), std::move(last));
} }
}; };
// Detection: whether IteratorType and SentinelType can be compared with !=
template<typename IteratorType, typename SentinelType, typename = void>
struct can_compare_ne_impl : std::false_type {};
template<typename IteratorType, typename SentinelType>
struct can_compare_ne_impl < IteratorType, SentinelType,
void_t < decltype(std::declval<IteratorType>() != std::declval<SentinelType>()) >>
: std::true_type {};
// Workaround for reversed operator order
template<typename IteratorType, typename SentinelType, typename = void>
struct can_compare_ne_reversed : std::false_type {};
template<typename IteratorType, typename SentinelType>
struct can_compare_ne_reversed < IteratorType, SentinelType,
void_t < decltype(std::declval<SentinelType>() != std::declval<IteratorType>()) >>
: std::true_type {};
template<typename IteratorType, typename SentinelType>
struct can_compare_ne_either_order : std::integral_constant < bool,
can_compare_ne_impl<IteratorType, SentinelType>::value ||
can_compare_ne_reversed<IteratorType, SentinelType>::value > {};
// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a
// trailing default argument (e.g. parse(s, nullptr, ...)) must never be
// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably
// SFINAE the `operator!=` detection above for std::nullptr_t against
// container/string types, which would otherwise make such calls ambiguous
// with the compatible-input overload.
template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool,
!std::is_same<SentinelType, std::nullptr_t>::value &&
can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
template<typename T> template<typename T>
struct is_iterator_of_multibyte struct is_iterator_of_multibyte
{ {
@@ -592,52 +476,28 @@ struct is_iterator_of_multibyte
}; };
}; };
template<typename IteratorType, typename SentinelType> template<typename IteratorType>
struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>> struct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>
{ {
using iterator_type = IteratorType; using iterator_type = IteratorType;
using sentinel_type = SentinelType;
using char_type = typename std::iterator_traits<iterator_type>::value_type; using char_type = typename std::iterator_traits<iterator_type>::value_type;
using base_adapter_type = iterator_input_adapter<iterator_type, sentinel_type>; using base_adapter_type = iterator_input_adapter<iterator_type>;
using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>; using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;
static adapter_type create(IteratorType first, SentinelType last) static adapter_type create(IteratorType first, IteratorType last)
{ {
return adapter_type(base_adapter_type(std::move(first), std::move(last))); return adapter_type(base_adapter_type(std::move(first), std::move(last)));
} }
}; };
// General purpose iterator-based input (iterator+sentinel pair; SentinelType // General purpose iterator-based input
// defaults to IteratorType for the common same-type case, but may differ for template<typename IteratorType>
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can typename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
// be compared with !=.
template < typename IteratorType, typename SentinelType = IteratorType,
typename = typename std::enable_if <
can_compare_ne<IteratorType, SentinelType>::value >::type >
typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_type input_adapter(IteratorType first, SentinelType last)
{ {
using factory_type = iterator_input_adapter_factory<IteratorType, SentinelType>; using factory_type = iterator_input_adapter_factory<IteratorType>;
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
@@ -665,32 +525,12 @@ struct container_input_adapter_factory< ContainerType,
} // namespace container_input_adapter_factory_impl } // namespace container_input_adapter_factory_impl
// General container path (iterator-based). Contiguous single-byte containers template<typename ContainerType>
// are excluded here and routed through the pointer-based overload below. typename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(ContainerType&& container)
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>()));
+15 -48
View File
@@ -370,10 +370,8 @@ class json_sax_dom_parser
case value_t::string: case value_t::string:
{ {
// escape sequences make the token longer than the value it // include the length of the quotes, which is 2
// parses to, so the start position cannot be derived from v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
// the value; use the offset the lexer recorded instead
v.start_position = m_lexer_ref->get_token_start_position();
break; break;
} }
@@ -628,7 +626,14 @@ class json_sax_dom_callback_parser
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured()) if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
{ {
// remove discarded value // remove discarded value
remove_discarded_value(*ref_stack.back()); for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)
{
if (it->is_discarded())
{
ref_stack.back()->erase(it);
break;
}
}
} }
return true; return true;
@@ -669,9 +674,8 @@ class json_sax_dom_callback_parser
bool end_array() bool end_array()
{ {
bool keep = true; bool keep = true;
const bool stored = ref_stack.back() != nullptr;
if (stored) if (ref_stack.back())
{ {
keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back()); keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());
if (keep) if (keep)
@@ -705,19 +709,9 @@ class json_sax_dom_callback_parser
keep_stack.pop_back(); keep_stack.pop_back();
// remove discarded value // remove discarded value
if (!ref_stack.empty() && ref_stack.back()) if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())
{ {
if (!keep && ref_stack.back()->is_array()) ref_stack.back()->m_data.m_value.array->pop_back();
{
ref_stack.back()->m_data.m_value.array->pop_back();
}
else if ((!keep || !stored) && ref_stack.back()->is_object())
{
// the array is either still stored under its key or was never
// stored, leaving the placeholder key() wrote; both show up as
// a discarded member of the parent object
remove_discarded_value(*ref_stack.back());
}
} }
return true; return true;
@@ -771,10 +765,8 @@ class json_sax_dom_callback_parser
case value_t::string: case value_t::string:
{ {
// escape sequences make the token longer than the value it // include the length of the quotes, which is 2
// parses to, so the start position cannot be derived from v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;
// the value; use the offset the lexer recorded instead
v.start_position = m_lexer_ref->get_token_start_position();
break; break;
} }
@@ -809,19 +801,6 @@ class json_sax_dom_callback_parser
} }
#endif #endif
/// remove the discarded value the callback rejected from its parent
static void remove_discarded_value(BasicJsonType& parent)
{
for (auto it = parent.begin(); it != parent.end(); ++it)
{
if (it->is_discarded())
{
parent.erase(it);
break;
}
}
}
/*! /*!
@param[in] v value to add to the JSON value we build during parsing @param[in] v value to add to the JSON value we build during parsing
@param[in] skip_callback whether we should skip calling the callback @param[in] skip_callback whether we should skip calling the callback
@@ -862,18 +841,6 @@ class json_sax_dom_callback_parser
// do not handle this value if we just learnt it shall be discarded // do not handle this value if we just learnt it shall be discarded
if (!keep) if (!keep)
{ {
// if the value was to become an object member, key() already
// stored a placeholder for it that has to be removed again
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_object())
{
JSON_ASSERT(!key_keep_stack.empty());
const bool placeholder_stored = key_keep_stack.back();
key_keep_stack.pop_back();
if (placeholder_stored)
{
remove_discarded_value(*ref_stack.back());
}
}
return {false, nullptr}; return {false, nullptr};
} }
+27 -309
View File
@@ -19,9 +19,7 @@
#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>
@@ -127,25 +125,6 @@ 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
@@ -167,14 +146,6 @@ 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;
@@ -294,40 +265,6 @@ 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
@@ -353,10 +290,6 @@ 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())
{ {
@@ -1346,78 +1279,45 @@ scan_number_done:
// we are done scanning a number) // we are done scanning a number)
unget(); unget();
return convert_number(number_type); char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)
} errno = 0;
/*! // try to parse integers first and fall back to floats
@brief convert an already-validated integer token to its value
The digit sequence in [first, last) has been validated by the caller, so a
dedicated parser can avoid the locale/errno overhead of std::strtoull.
@return the token type on success; token_type::uninitialized if @a
number_type is not an integer type or the value does not fit, in
which case the caller falls back to the floating-point conversion
(matching the previous std::strtoull/std::strtoll behavior)
*/
token_type convert_integer(token_type number_type, const char* first, const char* last)
{
if (number_type == token_type::value_unsigned) if (number_type == token_type::value_unsigned)
{ {
if (parse_integer_unsigned(first, last, value_unsigned)) const auto x = std::strtoull(token_buffer.data(), &endptr, 10);
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
if (errno != ERANGE)
{ {
return token_type::value_unsigned; value_unsigned = static_cast<number_unsigned_t>(x);
if (value_unsigned == x)
{
return token_type::value_unsigned;
}
} }
} }
else if (number_type == token_type::value_integer) else if (number_type == token_type::value_integer)
{ {
if (parse_integer_signed(first, last, value_integer)) const auto x = std::strtoll(token_buffer.data(), &endptr, 10);
// we checked the number format before
JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());
if (errno != ERANGE)
{ {
return token_type::value_integer; value_integer = static_cast<number_integer_t>(x);
} if (value_integer == x)
} {
return token_type::value_integer;
return token_type::uninitialized; }
}
/*!
@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();
if (number_type != token_type::value_float)
{
const token_type integer_result = convert_integer(number_type, num_begin, num_end);
if (integer_result != token_type::uninitialized)
{
return integer_result;
} }
} }
// this code is reached if we parse a floating-point number or if an // this code is reached if we parse a floating-point number or if an
// integer conversion above overflowed. Prefer std::from_chars // integer conversion above failed
// (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
@@ -1426,153 +1326,6 @@ 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;
// reset() records where this token starts (for diagnostics), so it has
// to run before the input position advances below
reset();
// An integer token needs no token_buffer: the SAX callbacks for
// number_integer/number_unsigned take only the value, and the overflow
// diagnostic rebuilds the text from the input. Convert straight from the
// input buffer and leave token_buffer empty. (JSON_DIAGNOSTIC_POSITIONS
// derives a number's start position from get_string().size(), so there
// the token still has to be materialized.)
#if !JSON_DIAGNOSTIC_POSITIONS
if (number_type != token_type::value_float)
{
const token_type integer_result = convert_integer(number_type, data, data + len);
if (JSON_HEDLEY_LIKELY(integer_result != token_type::uninitialized))
{
ia.bulk_skip(len - 1);
position.chars_read_total += (len - 1);
position.chars_read_current_line += (len - 1);
return integer_result;
}
// the value overflowed: fall through and let the float tail handle it
}
#endif
// materialize the token exactly as scan_number() would, substituting the
// locale decimal point so convert_number()'s strtof fallback stays valid.
// reset() already cleared token_buffer, so append() fills it (assign() is
// avoided because custom string_t types need not provide it)
token_buffer.append(reinterpret_cast<const typename string_t::value_type*>(data), len);
if (dot_index != std::string::npos)
{
token_buffer[dot_index] = static_cast<typename string_t::value_type>(decimal_point_char);
decimal_point_position = dot_index;
}
ia.bulk_skip(len - 1);
position.chars_read_total += (len - 1);
position.chars_read_current_line += (len - 1);
return convert_number(number_type);
}
/// 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
@@ -1604,11 +1357,6 @@ scan_number_done:
token_buffer.clear(); token_buffer.clear();
decimal_point_position = std::string::npos; decimal_point_position = std::string::npos;
#if JSON_DIAGNOSTIC_POSITIONS
// the first character of the token has already been read, hence the -1
token_start_position = position.chars_read_total - 1;
#endif
note_token_start(std::integral_constant<bool, lazy_token_string> {}); note_token_start(std::integral_constant<bool, lazy_token_string> {});
} }
@@ -1660,9 +1408,6 @@ scan_number_done:
if (current == '\n') if (current == '\n')
{ {
++position.lines_read; ++position.lines_read;
// remember the column the newline was read at: chars_read_current_line
// is about to be cleared, and a matching unget() cannot reconstruct it
chars_read_before_newline = position.chars_read_current_line;
position.chars_read_current_line = 0; position.chars_read_current_line = 0;
} }
@@ -1696,20 +1441,12 @@ scan_number_done:
--position.chars_read_total; --position.chars_read_total;
// in case we "unget" a newline, we have to also decrement the lines_read // in case we "unget" a newline, we have to also decrement the lines_read
// and restore the column that get() cleared when it saw the newline;
// chars_read_current_line == 0 can only mean the last get() read one
if (position.chars_read_current_line == 0) if (position.chars_read_current_line == 0)
{ {
if (position.lines_read > 0) if (position.lines_read > 0)
{ {
--position.lines_read; --position.lines_read;
} }
// chars_read_before_newline counts the newline itself, which is the
// character being ungotten, hence the -1
position.chars_read_current_line = (chars_read_before_newline > 0)
? chars_read_before_newline - 1
: 0;
} }
else else
{ {
@@ -1782,15 +1519,6 @@ scan_number_done:
return position; return position;
} }
#if JSON_DIAGNOSTIC_POSITIONS
/// return the offset of the first character of the last read token; unlike
/// the token's parsed value, this accounts for escape sequences
constexpr std::size_t get_token_start_position() const noexcept
{
return token_start_position;
}
#endif
/// seekable adapter: rebuild the last read token from the input on demand /// seekable adapter: rebuild the last read token from the input on demand
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const const std::vector<char_type>& collect_token_chars(std::vector<char_type>& out, std::true_type /*lazy*/) const
{ {
@@ -1952,7 +1680,7 @@ scan_number_done:
case '7': case '7':
case '8': case '8':
case '9': case '9':
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {}); return scan_number();
// 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)
@@ -1983,10 +1711,6 @@ scan_number_done:
/// the start position of the current token /// the start position of the current token
position_t position {}; position_t position {};
/// the value chars_read_current_line had when the last newline was read, so
/// that unget() can restore the column instead of leaving it at 0
std::size_t chars_read_before_newline = 0;
/// raw input token string for error messages; only populated for streaming /// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start) /// adapters (seekable adapters reconstruct it lazily via token_string_start)
std::vector<char_type> token_string {}; std::vector<char_type> token_string {};
@@ -1995,12 +1719,6 @@ scan_number_done:
/// the last read token on error for seekable adapters (see collect_token_chars) /// the last read token on error for seekable adapters (see collect_token_chars)
std::size_t token_string_start = 0; std::size_t token_string_start = 0;
#if JSON_DIAGNOSTIC_POSITIONS
/// start offset of the current token within the input, used to report
/// diagnostic positions (see reset())
std::size_t token_start_position = 0;
#endif
/// buffer for variable-length tokens (numbers, strings) /// buffer for variable-length tokens (numbers, strings)
string_t token_buffer {}; string_t token_buffer {};
@@ -1,302 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | 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
@@ -1,289 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | 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
+11 -27
View File
@@ -231,9 +231,7 @@ struct char_traits<signed char> : std::char_traits<char>
// Redefine to_int_type function // Redefine to_int_type function
static int_type to_int_type(char_type c) noexcept static int_type to_int_type(char_type c) noexcept
{ {
// cast via unsigned char: sign-extending a negative char_type would make return static_cast<int_type>(c);
// byte 0xFF indistinguishable from eof()
return static_cast<int_type>(static_cast<unsigned char>(c));
} }
static char_type to_char_type(int_type i) noexcept static char_type to_char_type(int_type i) noexcept
@@ -701,34 +699,20 @@ struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>> : std::true_type {};
template <typename A> template <typename A>
struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {}; struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>&> : std::true_type {};
// checks if A and B are comparable using Compare functor, assuming that
// neither A nor B is a json_pointer type (that case is handled by
// is_comparable below, which never instantiates this helper otherwise)
template<typename Compare, typename A, typename B, typename = void>
struct is_comparable_no_json_pointer : std::false_type {};
template<typename Compare, typename A, typename B>
struct is_comparable_no_json_pointer < Compare, A, B, enable_if_t <
std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
>> : std::true_type {};
// checks if A and B are comparable using Compare functor // checks if A and B are comparable using Compare functor
// We dispatch on is_json_pointer_of as a plain bool (rather than folding it template<typename Compare, typename A, typename B, typename = void>
// into a single enable_if_t condition together with the checks below) so
// that the Compare(A, B) checks are only ever written - and thus only ever
// instantiated - when A/B are not a json_pointer/string pair. Those checks
// use json_pointer::operator string_t() (GCC, see #4621) resp. the
// deprecated json_pointer/string operator== (Clang, see #5288), and merely
// naming them as later operands of a plain && chain is not sufficient to
// avoid their instantiation on all compilers, even when the first operand
// is false. The dispatch on is_json_pointer_of can be removed once the
// deprecated json_pointer comparison operators have been removed.
template<typename Compare, typename A, typename B, bool = is_json_pointer_of<A, B>::value>
struct is_comparable : std::false_type {}; struct is_comparable : std::false_type {};
// We exclude json_pointer here, because the checks using Compare(A, B) will
// use json_pointer::operator string_t() which triggers a deprecation warning
// for GCC. See https://github.com/nlohmann/json/issues/4621. The call to
// is_json_pointer_of can be removed once the deprecated function has been
// removed.
template<typename Compare, typename A, typename B> template<typename Compare, typename A, typename B>
struct is_comparable<Compare, A, B, false> : is_comparable_no_json_pointer<Compare, A, B> {}; struct is_comparable < Compare, A, B, enable_if_t < !is_json_pointer_of<A, B>::value
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>()))>::value
&& std::is_constructible <decltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))>::value
>> : std::true_type {};
template<typename T> template<typename T>
using detect_is_transparent = typename T::is_transparent; using detect_is_transparent = typename T::is_transparent;
@@ -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)
{ {
@@ -1662,31 +1647,7 @@ class binary_writer
std::size_t len = (value.at(key).empty() ? 0 : 1); std::size_t len = (value.at(key).empty() ? 0 : 1);
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
// a dimension is read as an unsigned value below, so anything that len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);
// is not a non-negative integer is rejected: a non-integer entry
// would pun unrelated bytes as the dimension, and a negative one
// would wrap into a nonsensical length
if (!el.is_number_integer() || (!el.is_number_unsigned() && el.template get<std::int64_t>() < 0))
{
return true;
}
// a dimension that does not fit into std::size_t, or a product that
// overflows it, would wrap around and could match the size of
// _ArrayData_ by accident; the resulting header announces an
// element count that no reader can honor (the binary reader rejects
// it with out_of_range.408), so encode as a plain object instead
const auto dim = el.template get<std::uint64_t>();
if (!value_in_range_of<std::size_t>(dim))
{
return true;
}
const auto dim_size = static_cast<std::size_t>(dim);
if (dim_size != 0 && len > (std::numeric_limits<std::size_t>::max)() / dim_size)
{
return true;
}
len *= dim_size;
} }
key = "_ArrayData_"; key = "_ArrayData_";
@@ -1695,24 +1656,6 @@ class binary_writer
return true; return true;
} }
// every element is written below as the number kind dtype names, so it
// has to actually be a number of that category: an element of any other
// type would reinterpret unrelated bytes, e.g. a string's heap pointer,
// as that number. Such an object falls back to a plain object encoding.
// dtype names the wire type, not the storage type: whether an integer
// is held as number_integer or number_unsigned depends on how the value
// was built (parsing stores non-negative integers as unsigned, the C++
// API stores int literals as signed), so both are accepted here and the
// writes below go through get<>, which reads the member that is active.
const bool ndarray_is_float = (dtype == 'd' || dtype == 'D');
for (const auto& el : value.at(key))
{
if (ndarray_is_float ? !el.is_number_float() : !el.is_number_integer())
{
return true;
}
}
oa->write_character('['); oa->write_character('[');
oa->write_character('$'); oa->write_character('$');
oa->write_character(dtype); oa->write_character(dtype);
@@ -1726,70 +1669,70 @@ class binary_writer
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::uint8_t>(el.template get<std::uint64_t>()), true); write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true);
} }
} }
else if (dtype == 'i') else if (dtype == 'i')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::int8_t>(el.template get<std::int64_t>()), true); write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true);
} }
} }
else if (dtype == 'u') else if (dtype == 'u')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::uint16_t>(el.template get<std::uint64_t>()), true); write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true);
} }
} }
else if (dtype == 'I') else if (dtype == 'I')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::int16_t>(el.template get<std::int64_t>()), true); write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true);
} }
} }
else if (dtype == 'm') else if (dtype == 'm')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::uint32_t>(el.template get<std::uint64_t>()), true); write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true);
} }
} }
else if (dtype == 'l') else if (dtype == 'l')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<std::int32_t>(el.template get<std::int64_t>()), true); write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true);
} }
} }
else if (dtype == 'M') else if (dtype == 'M')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(el.template get<std::uint64_t>(), true); write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true);
} }
} }
else if (dtype == 'L') else if (dtype == 'L')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(el.template get<std::int64_t>(), true); write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true);
} }
} }
else if (dtype == 'd') else if (dtype == 'd')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(static_cast<float>(el.template get<double>()), true); write_number(static_cast<float>(el.m_data.m_value.number_float), true);
} }
} }
else if (dtype == 'D') else if (dtype == 'D')
{ {
for (const auto& el : value.at(key)) for (const auto& el : value.at(key))
{ {
write_number(el.template get<double>(), true); write_number(static_cast<double>(el.m_data.m_value.number_float), true);
} }
} }
return false; return false;
File diff suppressed because it is too large Load Diff
+28 -46
View File
@@ -1345,7 +1345,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
if (indent >= 0) if (indent >= 0)
{ {
s.dump(*this, true, ensure_ascii, static_cast<std::size_t>(indent)); s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));
} }
else else
{ {
@@ -3652,12 +3652,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// note parentheses around operands are necessary; see // note parentheses around operands are necessary; see
// https://github.com/nlohmann/json/issues/1530 // https://github.com/nlohmann/json/issues/1530
// Mixed signed/unsigned integer comparisons check whether the signed value
// is negative before casting. If it is, the comparison is performed with
// the fixed values -1 and 1, which preserves the ordering relationship
// because any negative signed value is smaller than any unsigned value.
// Otherwise, the non-negative signed value is cast to unsigned before the
// comparison to avoid wraparound.
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ #define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \
const auto lhs_type = lhs.type(); \ const auto lhs_type = lhs.type(); \
const auto rhs_type = rhs.type(); \ const auto rhs_type = rhs.type(); \
@@ -3716,16 +3710,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
} \ } \
else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \
{ \ { \
return (rhs.m_data.m_value.number_integer < 0) \ return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \
? (number_integer_t(1) op number_integer_t(-1)) \
: (lhs.m_data.m_value.number_unsigned op static_cast<number_unsigned_t>(rhs.m_data.m_value.number_integer)); \
} \ } \
else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \
{ \ { \
return (lhs.m_data.m_value.number_integer < 0) \ return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \
? (number_integer_t(-1) op number_integer_t(1)) \ } \
: (static_cast<number_unsigned_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_unsigned); \
} \
else if(compares_unordered(lhs, rhs))\ else if(compares_unordered(lhs, rhs))\
{\ {\
return (unordered_result);\ return (unordered_result);\
@@ -4093,13 +4083,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result; return result;
} }
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support) /// @brief deserialize from a pair of character iterators
/// @sa https://json.nlohmann.me/api/basic_json/parse/ /// @sa https://json.nlohmann.me/api/basic_json/parse/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(IteratorType first, static basic_json parse(IteratorType first,
SentinelType last, IteratorType last,
parser_callback_t cb = nullptr, parser_callback_t cb = nullptr,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -4133,11 +4122,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true); return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
} }
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief check if the input is valid JSON
/// @sa https://json.nlohmann.me/api/basic_json/accept/ /// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0> static bool accept(IteratorType first, IteratorType last,
static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false, const bool ignore_comments = false,
const bool ignore_trailing_commas = false) const bool ignore_trailing_commas = false)
{ {
@@ -4169,12 +4157,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict); : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
} }
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief generate SAX events
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/ /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
template<class IteratorType, class SAX, class SentinelType = IteratorType, template<class IteratorType, class SAX>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_NON_NULL(3) JSON_HEDLEY_NON_NULL(3)
static bool sax_parse(IteratorType first, SentinelType last, SAX* sax, static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,
input_format_t format = input_format_t::json, input_format_t format = input_format_t::json,
const bool strict = true, const bool strict = true,
const bool ignore_comments = false, const bool ignore_comments = false,
@@ -4474,12 +4461,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); return res ? result : basic_json(value_t::discarded);
} }
/// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in CBOR format
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/ /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_cbor(IteratorType first, SentinelType last, static basic_json from_cbor(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true, const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error) const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
@@ -4532,12 +4518,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); return res ? result : basic_json(value_t::discarded);
} }
/// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in MessagePack format
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/ /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_msgpack(IteratorType first, SentinelType last, static basic_json from_msgpack(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4587,12 +4572,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); return res ? result : basic_json(value_t::discarded);
} }
/// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in UBJSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/ /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_ubjson(IteratorType first, SentinelType last, static basic_json from_ubjson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4642,12 +4626,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); return res ? result : basic_json(value_t::discarded);
} }
/// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BJData format
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/ /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(IteratorType first, SentinelType last, static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
@@ -4673,12 +4656,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded); return res ? result : basic_json(value_t::discarded);
} }
/// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support) /// @brief create a JSON value from an input in BSON format
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/ /// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename IteratorType, typename SentinelType = IteratorType, template<typename IteratorType>
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bson(IteratorType first, SentinelType last, static basic_json from_bson(IteratorType first, IteratorType last,
const bool strict = true, const bool strict = true,
const bool allow_exceptions = true) const bool allow_exceptions = true)
{ {
File diff suppressed because it is too large Load Diff
-13
View File
@@ -30,17 +30,4 @@ inline std::vector<std::uint8_t> read_binary_file(const std::string& filename)
return byte_vector; return byte_vector;
} }
// sentinel for istreambuf_iterator; compares != true until EOF is reached
// lets tests read a file directly via the new iterator+sentinel overloads
// instead of buffering the whole file into a vector first.
// Only the iterator-first direction (it != sentinel) is ever evaluated by
// the library's parse loop, so no reversed-order overload is needed.
struct istreambuf_sentinel
{
friend bool operator!=(const std::istreambuf_iterator<char>& it, const istreambuf_sentinel& /*unused*/) noexcept
{
return it != std::istreambuf_iterator<char>();
}
};
} // namespace utils } // namespace utils
-34
View File
@@ -132,37 +132,3 @@ TEST_CASE("BJData")
} }
} }
} }
TEST_CASE("CBOR")
{
SECTION("parse errors")
{
SECTION("array/map size larger than std::size_t")
{
// declared lengths do not fit in a 32-bit std::size_t and must not be truncated
std::vector<uint8_t> const varr = {0x9B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05};
std::vector<uint8_t> const vmap = {0xBB, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(varr), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
CHECK(json::from_cbor(varr, true, false).is_discarded());
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vmap), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
CHECK(json::from_cbor(vmap, true, false).is_discarded());
}
SECTION("array/map size equal to the indefinite-length sentinel")
{
// on 32-bit platforms a four-byte length of 0xFFFFFFFF aliases unknown_size()
std::vector<uint8_t> const varr = {0x9A, 0xFF, 0xFF, 0xFF, 0xFF};
std::vector<uint8_t> const vmap = {0xBA, 0xFF, 0xFF, 0xFF, 0xFF};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_cbor(varr), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
CHECK(json::from_cbor(varr, true, false).is_discarded());
CHECK_THROWS_WITH_AS(_ = json::from_cbor(vmap), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
CHECK(json::from_cbor(vmap, true, false).is_discarded());
}
}
}
-108
View File
@@ -1347,8 +1347,6 @@ TEST_CASE("BJData")
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error);
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'}; std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error);
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range);
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'}; std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData size: expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x02", json::parse_error);
} }
@@ -2589,69 +2587,6 @@ TEST_CASE("BJData")
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B); CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
} }
SECTION("ndarray with data not matching _ArrayType_ is written as an object")
{
// A JData-annotated object is only serialized as an ndarray when
// its _ArrayData_ elements are actually stored as the number kind
// named by _ArrayType_. Otherwise the writer would read the wrong
// union member (e.g. a std::string's heap pointer as a uint64) and
// emit it, so such an object falls back to a plain object encoding
// that still round-trips.
// string data declared as a uint64 array
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {1}}, {"_ArrayData_", {"pointer"}}});
const auto out_str = json::to_bjdata(j_str);
CHECK(out_str.at(0) == '{');
CHECK(json::from_bjdata(out_str) == j_str);
// integer data declared as a double array
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
const auto out_float = json::to_bjdata(j_float);
CHECK(out_float.at(0) == '{');
CHECK(json::from_bjdata(out_float) == j_float);
// a non-integer shape entry is likewise not treated as an ndarray
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x"}}, {"_ArrayData_", {1}}});
const auto out_size = json::to_bjdata(j_size);
CHECK(out_size.at(0) == '{');
CHECK(json::from_bjdata(out_size) == j_size);
// a negative shape entry is not a usable dimension either
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1],"_ArrayData_":[1]})");
const auto out_neg = json::to_bjdata(j_neg);
CHECK(out_neg.at(0) == '{');
CHECK(json::from_bjdata(out_neg) == j_neg);
}
SECTION("ndarray parsed from text is written as a typed array")
{
// json::parse stores a non-negative integer as number_unsigned while
// the C++ API stores an int literal as number_integer, so _ArrayType_
// names the wire type rather than the storage. Both storages have to
// produce the same typed array for every type.
for (const char* type :
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char", "byte"
})
{
CAPTURE(type);
const std::string text = std::string(R"({"_ArrayType_":")") + type +
R"(","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
const auto from_text = json::to_bjdata(json::parse(text));
CHECK(from_text.at(0) == '[');
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", type}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}})));
}
// negative values under a signed type behave the same way
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2],"_ArrayData_":[-5,7]})"));
CHECK(from_neg.at(0) == '[');
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-5, 7}}})));
// and so do the floating point types
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2],"_ArrayData_":[1.5,2.5]})"));
CHECK(from_float.at(0) == '[');
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 2.5}}})));
}
SECTION("optimized ndarray (type and vector-size as 1D array)") SECTION("optimized ndarray (type and vector-size as 1D array)")
{ {
// create vector with two elements of the same type // create vector with two elements of the same type
@@ -2730,27 +2665,6 @@ TEST_CASE("BJData")
CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type); CHECK(json::from_bjdata(json::to_bjdata(j_type), true, true) == j_type);
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size); CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
} }
SECTION("ndarray whose dimensions overflow stays as object")
{
// the product of the dimensions wraps around std::size_t to 0
// and so matches the size of the empty _ArrayData_; writing this
// as an ndarray would announce an element count no reader can
// honor, so it has to stay a plain object
json j_overflow = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {9223372036854775808ull, 2}}, {"_ArrayType_", "uint8"}});
CHECK(json::from_bjdata(json::to_bjdata(j_overflow), true, true) == j_overflow);
// a single dimension that does not fit into std::size_t is
// rejected for the same reason (only observable where
// std::size_t is narrower than 64 bit)
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
// a well-formed ndarray is still encoded as one
json j_ok = json({{"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"_ArraySize_", {2, 3}}, {"_ArrayType_", "uint8"}});
CHECK(json::to_bjdata(j_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 3, ']', 1, 2, 3, 4, 5, 6}));
CHECK(json::from_bjdata(json::to_bjdata(j_ok), true, true) == j_ok);
}
} }
} }
@@ -2807,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
@@ -3798,15 +3699,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
} }
} }
TEST_CASE("Parse BJData directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.bjdata";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_bjdata(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("all BJData first bytes") TEST_CASE("all BJData first bytes")
{ {
-170
View File
@@ -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
@@ -529,41 +498,6 @@ TEST_CASE("BSON")
CHECK(json::from_bson(result, true, false) == j); CHECK(json::from_bson(result, true, false) == j);
} }
SECTION("non-empty object with binary member without subtype")
{
const size_t N = 10;
const auto s = std::vector<std::uint8_t>(N, 'x');
json const j =
{
{ "entry", json::binary(s) }
};
CHECK(!j.at("entry").get_binary().has_subtype());
std::vector<std::uint8_t> const expected =
{
0x1B, 0x00, 0x00, 0x00, // size (little endian)
0x05, // entry: binary
'e', 'n', 't', 'r', 'y', '\x00',
0x0A, 0x00, 0x00, 0x00, // size of binary (little endian)
0x00, // Generic binary subtype
0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
0x00 // end marker
};
const auto result = json::to_bson(j);
CHECK(result == expected);
// roundtrip adds the generic binary subtype
const auto roundtrip = json::from_bson(result);
CHECK(roundtrip != j);
CHECK(roundtrip.at("entry").get_binary().has_subtype());
CHECK(roundtrip.at("entry").get_binary().subtype() == 0);
CHECK(json::from_bson(result, true, false) == roundtrip);
}
SECTION("non-empty object with binary member with subtype") SECTION("non-empty object with binary member with subtype")
{ {
// an MD5 hash // an MD5 hash
@@ -860,41 +794,6 @@ TEST_CASE("Incomplete BSON Input")
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson)); CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
} }
SECTION("Incomplete BSON Input 5")
{
std::vector<std::uint8_t> const incomplete_bson =
{
0x09, 0x00, 0x00, 0x00, // size (little endian)
0x08, // entry: boolean
'b', '\x00' // key, unexpected EOF before the value
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
SaxCountdown scp(0);
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
}
SECTION("Incomplete BSON Input 6")
{
std::vector<std::uint8_t> const incomplete_bson =
{
0x0F, 0x00, 0x00, 0x00, // size (little endian)
0x05, // entry: binary
'b', '\x00', // key
0x00, 0x00, 0x00, 0x00 // length, unexpected EOF before the subtype
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(incomplete_bson), "[json.exception.parse_error.110] parse error at byte 12: syntax error while parsing BSON number: unexpected end of input", json::parse_error&);
CHECK(json::from_bson(incomplete_bson, true, false).is_discarded());
SaxCountdown scp(0);
CHECK(!json::sax_parse(incomplete_bson, &scp, json::input_format_t::bson));
}
SECTION("Improve coverage") SECTION("Improve coverage")
{ {
SECTION("key") SECTION("key")
@@ -955,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")
@@ -1365,19 +1208,6 @@ TEST_CASE("BSON numerical data")
} }
} }
TEST_CASE("Parse BSON directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json.org/1.json";
std::ifstream f_json(filename);
const json expected = json::parse(f_json);
std::ifstream file(filename + ".bson", std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_bson(first, utils::istreambuf_sentinel{});
CHECK(parsed == expected);
}
TEST_CASE("BSON roundtrips" * doctest::skip()) TEST_CASE("BSON roundtrips" * doctest::skip())
{ {
SECTION("reference files") SECTION("reference files")
+1 -46
View File
@@ -1921,15 +1921,6 @@ TEST_CASE("single CBOR roundtrip")
} }
} }
TEST_CASE("Parse CBOR directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.cbor";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_cbor(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("CBOR regressions") TEST_CASE("CBOR regressions")
{ {
@@ -1999,42 +1990,6 @@ TEST_CASE("CBOR regressions")
} }
#endif #endif
TEST_CASE("CBOR definite length equal to the indefinite-length sentinel")
{
// A definite-length array or map whose declared element count equals the
// reserved unknown_size() sentinel (SIZE_MAX) must be rejected. Otherwise
// it is read as an indefinite-length container and the following bytes are
// silently accepted instead of the (impossible) count being reported.
json _;
SECTION("array")
{
// 0x9B: array with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
const std::vector<uint8_t> input = {0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x02, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive array size", json::out_of_range&);
}
SECTION("map")
{
// 0xBB: map with eight-byte length; length = 0xFFFFFFFFFFFFFFFF
const std::vector<uint8_t> input = {0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x61, 0x61, 0x01, 0xFF};
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input), "[json.exception.out_of_range.408] syntax error while parsing CBOR size: excessive map size", json::out_of_range&);
}
SECTION("indefinite-length containers are unaffected")
{
CHECK(json::from_cbor(std::vector<uint8_t>({0x9F, 0x01, 0x02, 0xFF})) == json({1, 2}));
CHECK(json::from_cbor(std::vector<uint8_t>({0xBF, 0x61, 0x61, 0x01, 0xFF})) == json({{"a", 1}}));
}
SECTION("ordinary four-byte length containers are unaffected")
{
// 0x9A/0xBA carry a four-byte length; a normal count still parses
CHECK(json::from_cbor(std::vector<uint8_t>({0x9A, 0x00, 0x00, 0x00, 0x02, 0x01, 0x02})) == json({1, 2}));
CHECK(json::from_cbor(std::vector<uint8_t>({0xBA, 0x00, 0x00, 0x00, 0x01, 0x61, 0x61, 0x01})) == json({{"a", 1}}));
}
}
TEST_CASE("CBOR roundtrips" * doctest::skip()) TEST_CASE("CBOR roundtrips" * doctest::skip())
{ {
SECTION("input from flynn") SECTION("input from flynn")
@@ -2345,7 +2300,7 @@ TEST_CASE("all CBOR first bytes")
} }
#endif #endif
TEST_CASE("examples from RFC 8949 Appendix A") TEST_CASE("examples from RFC 7049 Appendix A")
{ {
SECTION("numbers") SECTION("numbers")
{ {
-371
View File
@@ -12,10 +12,6 @@
#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
@@ -228,370 +224,3 @@ 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));
}
}
#if !defined(JSON_NOEXCEPTION)
// these sections parse invalid input, which aborts when exceptions are off
SECTION("exhaustive grammar parity with the streaming path")
{
// The JSON number grammar is encoded twice: once as the scan_number()
// state machine and once as the contiguous fast path. Enumerate every
// short string over the number alphabet and require the two encodings to
// agree exactly - on acceptance, on the reported error, and on the parsed
// value - so they cannot drift apart.
const std::string alphabet = "01.eE+-";
// full outcome of parsing @a doc, so a mismatch in type, value, or error
// message is caught, not just a mismatch in acceptance
const auto outcome = [](const std::string & doc, bool streaming) -> std::string
{
try
{
if (streaming)
{
std::stringstream ss(doc);
const json j = json::parse(ss);
return std::string(j[0].type_name()) + '|' + j.dump();
}
const json j = json::parse(doc);
return std::string(j[0].type_name()) + '|' + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
std::vector<std::string> mismatches;
std::vector<std::string> tokens{""};
for (std::size_t length = 1; length <= 4; ++length)
{
std::vector<std::string> next;
next.reserve(tokens.size() * alphabet.size());
for (const auto& prefix : tokens)
{
for (const char c : alphabet)
{
next.push_back(prefix + c);
}
}
tokens = next;
for (const auto& token : tokens)
{
const std::string doc = "[" + token + "]";
if (outcome(doc, false) != outcome(doc, true))
{
mismatches.push_back(doc);
}
}
}
// 7 + 49 + 343 + 2401 tokens
CHECK(tokens.size() == 2401);
CAPTURE(mismatches);
CHECK(mismatches.empty());
}
SECTION("error positions match the streaming path")
{
// Rejecting identically is not enough: the fast path must also report the
// error at the same position as the byte path. A number directly followed
// by a newline is the interesting case, because the byte path reaches the
// newline (which resets the column) and then ungets it.
// returns the parse_error message, or "" if the document parsed
const auto contiguous_error = [](const std::string & doc) -> std::string
{
try
{
const json j = json::parse(doc);
static_cast<void>(j);
}
catch (const json::parse_error& e)
{
return {e.what()};
}
return {};
};
const auto streaming_error = [](const std::string & doc) -> std::string
{
try
{
std::stringstream ss(doc);
const json j = json::parse(ss);
static_cast<void>(j);
}
catch (const json::parse_error& e)
{
return {e.what()};
}
return {};
};
for (const char* bad :
{"[01\n]", "[00\n]", "[-01\n]", "{1\n}", "[1\n2]", "[1.2.3\n]",
"[1 \n2]", "[\n1\n2]", "1\n2", "[01\r\n]", "[1e\n]", "[-\n]"
})
{
CAPTURE(bad);
const std::string doc = bad;
const std::string contiguous_what = contiguous_error(doc);
CHECK_FALSE(contiguous_what.empty());
CHECK(contiguous_what == streaming_error(doc));
}
// the column must be the one the offending token actually starts at,
// not the 0 that an unget() across the newline used to leave behind
CHECK(contiguous_error("[01\n]") ==
"[json.exception.parse_error.101] parse error at line 1, column 3: "
"syntax error while parsing array - unexpected number literal; expected ']'");
}
#endif
}
TEST_CASE("lexer string fast path")
{
// Build a byte string from explicit values: a hex escape in a string
// literal swallows every following hex digit, which makes sequences like
// "\xC3\xA9b" mean something other than they look like.
const auto bytes = [](std::initializer_list<int> values)
{
std::string result;
for (const int value : values)
{
result.push_back(static_cast<char>(value));
}
return result;
};
#if !defined(JSON_NOEXCEPTION)
// the full outcome of parsing @a doc: the parsed value, or the exact error
// message, so a mismatch in either is caught. Only usable with exceptions
// on: parsing invalid input aborts when they are off.
const auto outcome = [](const std::string & doc, bool streaming) -> std::string
{
try
{
if (streaming)
{
std::stringstream ss(doc);
const json j = json::parse(ss);
return j.dump();
}
const json j = json::parse(doc);
return j.dump();
}
// not just parse_error: if a bulk scanner ever let ill-formed UTF-8
// through, dump() would throw type_error.316, and that has to surface
// as a reported mismatch rather than as an uncaught exception
catch (const json::exception& e)
{
return {e.what()};
}
};
#endif
// once at the start of the string, once past the first 8-byte SWAR word, so
// the bulk scanner sees each case with and without a run behind it
const std::vector<std::size_t> offsets{0, 9};
#if !defined(JSON_NOEXCEPTION)
SECTION("exhaustive contiguous vs streaming parity")
{
// ordinary ASCII, both specials, a control byte, characters that make
// the preceding backslash a valid escape, a UTF-8 lead byte of each
// length, a continuation byte, and a byte that is never valid
const std::vector<std::string> alphabet =
{
"a", "\"", "\\", "n", "u", "0", bytes({0x01}),
bytes({0xC3}), bytes({0xA9}), bytes({0xE4}), bytes({0xF0}),
bytes({0x80}), bytes({0xFF})
};
std::vector<std::string> mismatches;
std::vector<std::string> tokens{""};
for (std::size_t length = 1; length <= 3; ++length)
{
std::vector<std::string> next;
next.reserve(tokens.size() * alphabet.size());
for (const auto& prefix : tokens)
{
for (const auto& symbol : alphabet)
{
next.push_back(prefix + symbol);
}
}
tokens = next;
for (const auto& token : tokens)
{
for (const std::size_t offset : offsets)
{
const std::string doc = "[\"" + std::string(offset, 'a') + token + "\"]";
if (outcome(doc, false) != outcome(doc, true))
{
mismatches.push_back(doc);
}
}
}
}
// 13 + 169 + 2197 tokens, each at two offsets
CHECK(tokens.size() == 2197);
CAPTURE(mismatches);
CHECK(mismatches.empty());
}
SECTION("special bytes at every offset of the SWAR stride")
{
// The bulk scanner consumes 8 bytes at a time and then a tail; place
// every kind of byte that ends a run at each offset across two words,
// so multibyte sequences also straddle the word boundary.
const std::vector<std::string> specials =
{
"\"", "\\", bytes({0x01}), bytes({0x1F}), bytes({0x7F}),
bytes({0xC3, 0xA9}), bytes({0xE4, 0xB8, 0xAD}), bytes({0xF0, 0x9F, 0x98, 0x80}),
bytes({0xFF}), bytes({0xC3}), bytes({0xE4, 0xB8})
};
std::vector<std::string> mismatches;
for (std::size_t offset = 0; offset <= 17; ++offset)
{
for (const auto& special : specials)
{
const std::string doc = "[\"" + std::string(offset, 'a') + special + "\"]";
if (outcome(doc, false) != outcome(doc, true))
{
mismatches.push_back(doc);
}
}
}
CAPTURE(mismatches);
CHECK(mismatches.empty());
}
#endif
// json::accept() never throws, so the ranges stay covered without exceptions
SECTION("UTF-8 ranges are accepted and rejected as documented")
{
// The bulk validator must accept exactly what the byte-at-a-time
// scanner accepts, so pin the boundaries of every range it recognizes.
// aggregate, only ever brace-initialized below; default member
// initializers would stop it being an aggregate in C++11
struct utf8_case // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
{
std::string sequence;
bool valid;
const char* description;
};
const std::vector<utf8_case> cases =
{
{bytes({0xC2, 0x80}), true, "U+0080, shortest two-byte"},
{bytes({0xDF, 0xBF}), true, "U+07FF, longest two-byte"},
{bytes({0xC1, 0xBF}), false, "overlong two-byte"},
{bytes({0xC2, 0x7F}), false, "two-byte with bad continuation"},
{bytes({0xE0, 0xA0, 0x80}), true, "U+0800, shortest three-byte"},
{bytes({0xE0, 0x9F, 0xBF}), false, "overlong three-byte"},
{bytes({0xED, 0x9F, 0xBF}), true, "U+D7FF, just below the surrogates"},
{bytes({0xED, 0xA0, 0x80}), false, "surrogate U+D800"},
{bytes({0xED, 0xBF, 0xBF}), false, "surrogate U+DFFF"},
{bytes({0xEE, 0x80, 0x80}), true, "U+E000, just above the surrogates"},
{bytes({0xEF, 0xBF, 0xBF}), true, "U+FFFF"},
{bytes({0xF0, 0x90, 0x80, 0x80}), true, "U+10000, shortest four-byte"},
{bytes({0xF0, 0x8F, 0xBF, 0xBF}), false, "overlong four-byte"},
{bytes({0xF4, 0x8F, 0xBF, 0xBF}), true, "U+10FFFF, highest code point"},
{bytes({0xF4, 0x90, 0x80, 0x80}), false, "above U+10FFFF"},
{bytes({0xF5, 0x80, 0x80, 0x80}), false, "lead byte out of range"},
{bytes({0x80}), false, "bare continuation byte"},
{bytes({0xFF}), false, "byte that never appears in UTF-8"},
{bytes({0xC3}), false, "truncated two-byte"},
{bytes({0xE4, 0xB8}), false, "truncated three-byte"},
{bytes({0xF0, 0x9F, 0x98}), false, "truncated four-byte"}
};
for (const auto& test_case : cases)
{
CAPTURE(test_case.description);
for (const std::size_t offset : offsets)
{
CAPTURE(offset);
const std::string doc = "[\"" + std::string(offset, 'a') + test_case.sequence + "\"]";
CHECK(json::accept(doc) == test_case.valid);
#if !defined(JSON_NOEXCEPTION)
CHECK(outcome(doc, false) == outcome(doc, true));
#endif
}
}
}
}
-49
View File
@@ -1440,13 +1440,6 @@ TEST_CASE("parser class")
] ]
)"; )";
const auto* structured_object = R"(
{
"foo": [1, 2],
"bar": 3
}
)";
SECTION("filter nothing") SECTION("filter nothing")
{ {
const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept const json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json& /*unused*/) noexcept
@@ -1522,48 +1515,6 @@ TEST_CASE("parser class")
CHECK (j_filtered2 == json({1})); CHECK (j_filtered2 == json({1}));
} }
SECTION("filter array in object")
{
// the array is discarded once it is already stored under its key
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
{
return e != json::parse_event_t::array_end;
});
CHECK (j_filtered1 == json({{"bar", 3}}));
// the array is discarded before it is stored, leaving the
// placeholder the key event wrote
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json& /*parsed*/) noexcept
{
return e != json::parse_event_t::array_start;
});
CHECK (j_filtered2 == json({{"bar", 3}}));
}
SECTION("filter value in object")
{
// the value is discarded after its key was kept, leaving the
// placeholder the key event wrote
const json j_filtered1 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
{
return !(e == json::parse_event_t::value && parsed == json(3));
});
CHECK (j_filtered1 == json({{"foo", {1, 2}}}));
// the same value is discarded together with its key, so no
// placeholder was stored for it
const json j_filtered2 = json::parse(structured_object, [](int /*unused*/, json::parse_event_t e, const json & parsed) noexcept
{
return !((e == json::parse_event_t::key && parsed == json("bar")) ||
(e == json::parse_event_t::value && parsed == json(3)));
});
CHECK (j_filtered2 == json({{"foo", {1, 2}}}));
}
SECTION("filter specific events") SECTION("filter specific events")
{ {
SECTION("first closing event") SECTION("first closing event")
-71
View File
@@ -15,8 +15,6 @@
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <cstdint>
#define JSON_TESTS_PRIVATE #define JSON_TESTS_PRIVATE
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
@@ -257,75 +255,6 @@ TEST_CASE("lexicographical comparison operators")
{f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21
}; };
SECTION("signed/unsigned mixed comparison above INT64_MAX")
{
const json above_int64_max = static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1ULL;
const json max_uint64 = (std::numeric_limits<std::uint64_t>::max)();
const json negative_one = -1;
const json one = 1;
const json max_int64 = (std::numeric_limits<std::int64_t>::max)();
CHECK_FALSE(above_int64_max == negative_one);
CHECK(above_int64_max != negative_one);
CHECK(negative_one < above_int64_max);
CHECK(negative_one <= above_int64_max);
CHECK_FALSE(negative_one > above_int64_max);
CHECK_FALSE(negative_one >= above_int64_max);
CHECK_FALSE(above_int64_max < negative_one);
CHECK_FALSE(above_int64_max <= negative_one);
CHECK(above_int64_max > negative_one);
CHECK(above_int64_max >= negative_one);
CHECK(negative_one != above_int64_max);
CHECK_FALSE(negative_one == above_int64_max);
CHECK_FALSE(max_uint64 == negative_one);
CHECK(max_uint64 != negative_one);
CHECK(negative_one < max_uint64);
CHECK(negative_one <= max_uint64);
CHECK_FALSE(negative_one > max_uint64);
CHECK_FALSE(negative_one >= max_uint64);
CHECK_FALSE(max_uint64 < negative_one);
CHECK_FALSE(max_uint64 <= negative_one);
CHECK(max_uint64 > negative_one);
CHECK(max_uint64 >= negative_one);
CHECK(negative_one != max_uint64);
CHECK_FALSE(negative_one == max_uint64);
CHECK_FALSE(one == above_int64_max);
CHECK(one != above_int64_max);
CHECK(one < above_int64_max);
CHECK(one <= above_int64_max);
CHECK_FALSE(one > above_int64_max);
CHECK_FALSE(one >= above_int64_max);
CHECK_FALSE(above_int64_max < one);
CHECK_FALSE(above_int64_max <= one);
CHECK(above_int64_max > one);
CHECK(above_int64_max >= one);
CHECK_FALSE(max_int64 == above_int64_max);
CHECK(max_int64 != above_int64_max);
CHECK(max_int64 < above_int64_max);
CHECK(max_int64 <= above_int64_max);
CHECK_FALSE(max_int64 > above_int64_max);
CHECK_FALSE(max_int64 >= above_int64_max);
CHECK_FALSE(above_int64_max < max_int64);
CHECK_FALSE(above_int64_max <= max_int64);
CHECK(above_int64_max > max_int64);
CHECK(above_int64_max >= max_int64);
#if JSON_HAS_THREE_WAY_COMPARISON
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
CHECK((negative_one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
CHECK((above_int64_max <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
CHECK((negative_one <=> max_uint64) == std::partial_ordering::less); // *NOPAD*
CHECK((max_uint64 <=> negative_one) == std::partial_ordering::greater); // *NOPAD*
CHECK((one <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
CHECK((above_int64_max <=> one) == std::partial_ordering::greater); // *NOPAD*
CHECK((max_int64 <=> above_int64_max) == std::partial_ordering::less); // *NOPAD*
CHECK((above_int64_max <=> max_int64) == std::partial_ordering::greater); // *NOPAD*
#endif
}
SECTION("compares unordered") SECTION("compares unordered")
{ {
std::vector<std::vector<bool>> expected = std::vector<std::vector<bool>> expected =
-1
View File
@@ -100,7 +100,6 @@ 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
-15
View File
@@ -1782,21 +1782,6 @@ TEST_CASE("std::optional")
"[json.exception.type_error.302] type must be string, but is null", json::type_error&); "[json.exception.type_error.302] type must be string, but is null", json::type_error&);
CHECK_THROWS_WITH_AS(std::optional<int>(j_null), CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
"[json.exception.type_error.302] type must be number, but is null", json::type_error&); "[json.exception.type_error.302] type must be number, but is null", json::type_error&);
// Assignment goes through the same overload resolution as direct
// construction, so it throws for the same reason. This relies on
// basic_json's implicit conversion operator, so it only applies
// when JSON_USE_IMPLICIT_CONVERSIONS is enabled (the default).
#if JSON_USE_IMPLICIT_CONVERSIONS
std::optional<std::string> opt_assign;
CHECK_THROWS_WITH_AS(opt_assign = j_null,
"[json.exception.type_error.302] type must be string, but is null", json::type_error&);
#endif
// get_to() is the correct way to obtain std::nullopt from a JSON null.
std::optional<std::string> opt_get_to = "placeholder";
j_null.get_to(opt_get_to);
CHECK(opt_get_to == std::nullopt);
} }
SECTION("string") SECTION("string")
-24
View File
@@ -427,30 +427,6 @@ TEST_CASE("deserialization")
CHECK(l.events == std::vector<std::string>({"boolean(true)"})); CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
} }
SECTION("from std::vector<signed char>")
{
std::vector<signed char> const v = {'t', 'r', 'u', 'e'};
CHECK(json::parse(v) == json(true));
CHECK(json::accept(v));
SaxEventLogger l;
CHECK(json::sax_parse(v, &l));
CHECK(l.events.size() == 1);
CHECK(l.events == std::vector<std::string>({"boolean(true)"}));
// bytes outside ASCII are negative here and must not be sign-extended;
// 0xC3 and 0xA9 do not fit in signed char (MSVC C4309), so spell them as negative values
std::vector<signed char> const umlaut = {'"', static_cast<signed char>(0xC3 - 0x100), static_cast<signed char>(0xA9 - 0x100), '"'};
CHECK(json::parse(umlaut) == json("\xC3\xA9"));
CHECK(json::accept(umlaut));
// 0xFF (spelled as -1 to stay in range) must not be reported as end of input
std::vector<signed char> const trailing = {'t', 'r', 'u', 'e', static_cast<signed char>(0xFF - 0x100)};
json _;
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - invalid literal; last read: 'true\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
}
SECTION("from std::array") SECTION("from std::array")
{ {
std::array<uint8_t, 5> const v { {'t', 'r', 'u', 'e'} }; std::array<uint8_t, 5> const v { {'t', 'r', 'u', 'e'} };
-30
View File
@@ -38,36 +38,6 @@ TEST_CASE("Better diagnostics with positions")
"[json.exception.type_error.302] type must be number, but is string", json::type_error); "[json.exception.type_error.302] type must be number, but is string", json::type_error);
} }
SECTION("positions of strings containing escape sequences")
{
// escape sequences make the token longer than the string it parses to,
// so the positions must not be derived from the parsed value's length
const auto check = [](const std::string & text, const std::string & token)
{
CAPTURE(text)
CAPTURE(token)
const json j = json::parse(text);
const json& v = j.at("a");
CHECK(text.substr(v.start_pos(), v.end_pos() - v.start_pos()) == token);
};
check(R"({"a":"plain"})", R"("plain")");
check(R"({"a":"tab\there"})", R"("tab\there")");
check(R"({"a":"\n\n\n\n\n\n"})", R"("\n\n\n\n\n\n")");
check(R"({"a":"\""})", R"("\"")");
check(R"({"a":"\\"})", R"("\\")");
check(R"({"a":"é"})", R"("é")");
check(R"({"a":"🌞"})", R"("🌞")");
check("{\"a\":\"\xc3\xa9\"}", "\"\xc3\xa9\""); // multi-byte UTF-8, no escapes
// a string at the root, where an escape would otherwise push the
// reported start position past the opening quote
const std::string root = R"("a\tb")";
const json j = json::parse(root);
CHECK(j.start_pos() == 0);
CHECK(j.end_pos() == root.size());
}
SECTION("JSON patch add to primitive parent (#4292)") SECTION("JSON patch add to primitive parent (#4292)")
{ {
// the JSON Patch "add" target /foo/bar/baz has a string parent // the JSON Patch "add" target /foo/bar/baz has a string parent
-9
View File
@@ -1641,15 +1641,6 @@ TEST_CASE("single MessagePack roundtrip")
} }
} }
TEST_CASE("Parse MessagePack directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.msgpack";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_msgpack(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
TEST_CASE("MessagePack roundtrips" * doctest::skip()) TEST_CASE("MessagePack roundtrips" * doctest::skip())
{ {
SECTION("input from msgpack-python") SECTION("input from msgpack-python")
-25
View File
@@ -1530,29 +1530,4 @@ TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into A
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}})); CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
} }
TEST_CASE("issue #5338 - truncated CBOR tagged binary subtype is rejected")
{
const std::vector<std::vector<std::uint8_t>> truncated_tags =
{
{0xD8},
{0xD9, 0x00},
{0xDA, 0x00, 0x00, 0x00},
{0xDB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
};
for (const auto& data : truncated_tags)
{
CAPTURE(data);
for (const auto tag_handler :
{
json::cbor_tag_handler_t::ignore, json::cbor_tag_handler_t::store
})
{
CAPTURE(tag_handler);
const auto result = json::from_cbor(data, true, false, tag_handler);
CHECK(result.is_discarded());
}
}
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP DOCTEST_CLANG_SUPPRESS_WARNING_POP
-229
View File
@@ -382,232 +382,3 @@ 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\"");
}
}
TEST_CASE("indentation is written straight into the write buffer")
{
// put_indent() memsets the indentation into the write buffer instead of
// copying it out of a pre-grown indentation string. These cases cover an
// indentation wider than the buffer, a non-space indentation character, and
// nesting deep enough that the accumulated indentation spans several
// buffer-fulls - the situations the old grow-a-string approach got wrong.
SECTION("indent_step wider than the write buffer")
{
const json j = {{"a", 1}};
// 2000 > the 1024-byte write buffer, and > the 512 the indentation
// string used to start at
CHECK(j.dump(2000) == "{\n" + std::string(2000, ' ') + "\"a\": 1\n}");
// several whole buffer-fulls, so the buffer is refilled once and then
// flushed repeatedly
CHECK(j.dump(5000) == "{\n" + std::string(5000, ' ') + "\"a\": 1\n}");
CHECK(j.dump(5000, '\t') == "{\n" + std::string(5000, '\t') + "\"a\": 1\n}");
// an exact multiple of the buffer size
CHECK(j.dump(4096) == "{\n" + std::string(4096, ' ') + "\"a\": 1\n}");
}
SECTION("a non-space indentation character is used throughout")
{
const json j = {{"a", 1}};
// 600 is past the point where the indentation used to be grown, which
// is where a hard-coded space would have shown up
CHECK(j.dump(600, '\t') == "{\n" + std::string(600, '\t') + "\"a\": 1\n}");
CHECK(j.dump(3, '.') == "{\n...\"a\": 1\n}");
}
SECTION("accumulated indentation spans several buffer-fulls")
{
// five levels deep at 400 per level: the innermost value is indented by
// 2000 characters, reached in steps that each straddle the buffer end
json j = json::array({1});
for (int i = 0; i < 4; ++i)
{
j = json::array({j});
}
const std::string out = j.dump(400);
CHECK(out.find(std::string("\n") + std::string(2000, ' ') + "1\n") != std::string::npos);
CHECK(json::parse(out) == j);
}
SECTION("indentation is unchanged for ordinary widths")
{
const json j = {{"a", {1, 2}}, {"b", nullptr}};
CHECK(j.dump(2) == "{\n \"a\": [\n 1,\n 2\n ],\n \"b\": null\n}");
CHECK(j.dump(0) == "{\n\"a\": [\n1,\n2\n],\n\"b\": null\n}");
}
}
TEST_CASE("serialization of deeply nested values")
{
// dump() descends into a bounded number of levels and writes out whatever
// is nested deeper than that without the call stack; see
// https://github.com/nlohmann/json/issues/5387
SECTION("nested deeper than the call stack could follow")
{
// parsing is iterative, so building these costs little
const std::size_t depth = 100000;
const std::string array_text = std::string(depth, '[') + '0' + std::string(depth, ']');
CHECK(json::parse(array_text).dump() == array_text);
std::string object_text;
object_text.reserve((6 * depth) + 1);
for (std::size_t i = 0; i < depth; ++i)
{
object_text += "{\"a\":";
}
object_text += '1';
object_text.append(depth, '}');
CHECK(json::parse(object_text).dump() == object_text);
}
SECTION("depths around the bound of the descent")
{
// Cover every depth around the bound, so that the two ways of writing a
// value are known to meet cleanly - wherever the bound is set.
for (std::size_t d = 1; d <= 300; ++d)
{
CAPTURE(d);
const std::string array_text = std::string(d, '[') + '7' + std::string(d, ']');
CHECK(json::parse(array_text).dump() == array_text);
std::string object_text;
for (std::size_t i = 0; i < d; ++i)
{
object_text += "{\"k\":";
}
object_text += '7';
object_text.append(d, '}');
CHECK(json::parse(object_text).dump() == object_text);
}
}
SECTION("pretty-printing across the bound")
{
for (std::size_t d = 120; d <= 140; ++d)
{
CAPTURE(d);
const json j = json::parse(std::string(d, '[') + '7' + std::string(d, ']'));
std::string expected;
for (std::size_t i = 0; i < d; ++i)
{
expected += std::string(2 * i, ' ') + "[\n";
}
expected += std::string(2 * d, ' ') + '7';
for (std::size_t i = d; i > 0; --i)
{
expected += '\n' + std::string(2 * (i - 1), ' ') + ']';
}
CHECK(j.dump(2) == expected);
}
}
SECTION("an empty container below the bound")
{
// an empty container is written out in full and never descended into,
// so it must not gain a newline when it is reached iteratively
for (std::size_t d = 125; d <= 135; ++d)
{
CAPTURE(d);
const std::string compact = std::string(d, '[') + "[]" + std::string(d, ']');
CHECK(json::parse(compact).dump() == compact);
const std::string with_object = std::string(d, '[') + "{}" + std::string(d, ']');
CHECK(json::parse(with_object).dump() == with_object);
}
}
}
-30
View File
@@ -53,34 +53,4 @@ TEST_CASE("type traits")
// NOLINTEND(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) // NOLINTEND(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays)
} }
} }
SECTION("char_traits")
{
SECTION("to_int_type does not sign-extend")
{
using unsigned_traits = nlohmann::detail::char_traits<unsigned char>;
using signed_traits = nlohmann::detail::char_traits<signed char>;
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0x7F)) == 0x7F);
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0x80)) == 0x80);
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(0xFF)) == 0xFF);
CHECK(signed_traits::to_int_type(static_cast<signed char>(0x7F)) == 0x7F);
// 0x80 and 0xFF do not fit in signed char (MSVC C4309), so spell them as negative values
CHECK(signed_traits::to_int_type(static_cast<signed char>(0x80 - 0x100)) == 0x80);
CHECK(signed_traits::to_int_type(static_cast<signed char>(0xFF - 0x100)) == 0xFF);
}
SECTION("no byte value collides with eof")
{
using unsigned_traits = nlohmann::detail::char_traits<unsigned char>;
using signed_traits = nlohmann::detail::char_traits<signed char>;
for (int i = 0; i < 256; ++i)
{
CHECK(unsigned_traits::to_int_type(static_cast<unsigned char>(i)) != unsigned_traits::eof());
CHECK(signed_traits::to_int_type(static_cast<signed char>(i)) != signed_traits::eof());
}
}
}
} }
-74
View File
@@ -819,8 +819,6 @@ TEST_CASE("UBJSON")
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error);
std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'}; std::vector<uint8_t> const vec3 = {'H', 'i', 2, '1', '.'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error);
std::vector<uint8_t> const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range&);
std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'}; std::vector<uint8_t> const vec4 = {'H', 2, '1', '0'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x02", json::parse_error); CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec4), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON size: expected length type specification (U, i, I, l, L) after '#'; last byte: 0x02", json::parse_error);
} }
@@ -1713,44 +1711,6 @@ TEST_CASE("UBJSON")
CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i); CHECK(json::to_ubjson(json::from_ubjson(s_L)) == s_i);
} }
SECTION("no-op markers")
{
// A no-op ('N') is valid wherever a value may start; it is consumed
// by get_ignore_noop() before the value is read. It is not valid
// where a string length type specification is expected.
SECTION("accepted where a value may start")
{
// at top level, also repeated
CHECK(json::from_ubjson(std::vector<uint8_t>({'N', 'i', 1})) == json(1));
CHECK(json::from_ubjson(std::vector<uint8_t>({'N', 'N', 'N', 'i', 1})) == json(1));
// inside an array of unknown size, before and after an element
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', 'N', 'i', 1, ']'})) == json({1}));
CHECK(json::from_ubjson(std::vector<uint8_t>({'[', 'i', 1, 'N', ']'})) == json({1}));
// inside an object of unknown size: before a key, between key
// and value, and before the closing '}'
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'N', 'U', 1, 'a', 'i', 1, '}'})) == json({{"a", 1}}));
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'U', 1, 'a', 'N', 'i', 1, '}'})) == json({{"a", 1}}));
CHECK(json::from_ubjson(std::vector<uint8_t>({'{', 'U', 1, 'a', 'i', 1, 'N', '}'})) == json({{"a", 1}}));
}
SECTION("rejected where a length type specification is expected")
{
json _;
// after the 'S' marker of a string value
std::vector<uint8_t> const v_S = {'S', 'N', 'U', 1, 'a'};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_S), "[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: 0x4E", json::parse_error&);
// as the key length of an object with a known size, where
// no-ops are not permitted in the first place
std::vector<uint8_t> const v_key = {'{', '#', 'i', 1, 'N', 'U', 1, 'a', 'i', 1};
CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v_key), "[json.exception.parse_error.113] parse error at byte 5: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x4E", json::parse_error&);
}
}
SECTION("number") SECTION("number")
{ {
SECTION("float") SECTION("float")
@@ -1902,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")
@@ -2458,15 +2393,6 @@ TEST_CASE("Universal Binary JSON Specification Examples 1")
} }
} }
TEST_CASE("Parse UBJSON directly from a file using iterator and sentinel")
{
std::string const filename = TEST_DATA_DIRECTORY "/json_testsuite/sample.json.ubjson";
std::ifstream file(filename, std::ios::binary);
const std::istreambuf_iterator<char> first(file);
const json parsed = json::from_ubjson(first, utils::istreambuf_sentinel{});
CHECK((parsed.is_object() || parsed.is_array()));
}
#if !defined(JSON_NOEXCEPTION) #if !defined(JSON_NOEXCEPTION)
TEST_CASE("all UBJSON first bytes") TEST_CASE("all UBJSON first bytes")
{ {
-238
View File
@@ -6,25 +6,12 @@
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me> // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// cmake/test.cmake selects the C++ standard versions with which to build a
// unit test based on the presence of JSON_HAS_CPP_<VERSION> macros.
// When using macros that are only defined for particular versions of the standard
// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding
// version macro in a comment close by, like this:
// JSON_HAS_CPP_<VERSION> (do not remove; see note at top of file)
#include "doctest_compatibility.h" #include "doctest_compatibility.h"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
using nlohmann::json; using nlohmann::json;
#include <list> #include <list>
#include <string> // string
#include <vector> // vector
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
#include <iterator>
#endif
namespace namespace
{ {
@@ -181,229 +168,4 @@ TEST_CASE("Custom iterator")
CHECK(as_json.at(3) == 4); CHECK(as_json.at(3) == 4);
} }
// Custom sentinel type for testing heterogeneous iterator+sentinel support
struct CustomSentinel
{
const char* end_ptr;
// only the iterator-first direction (it != sentinel) is ever evaluated by
// the library's parse loop; a reversed-order overload would go unused and
// trip -Wunneeded-internal-declaration under -Weverything
friend bool operator!=(const char* it, const CustomSentinel& sentinel)
{
return it != sentinel.end_ptr;
}
};
TEST_CASE("Parse with heterogeneous iterator and sentinel types")
{
const std::string json_str = R"({"key":"value"})";
const char* end_ptr = json_str.data() + json_str.size();
// Parse using pointer and sentinel (different types)
json j = json::parse(json_str.data(), CustomSentinel{end_ptr});
CHECK(j["key"] == "value");
// Accept using pointer and sentinel
CHECK(json::accept(json_str.data(), CustomSentinel{end_ptr}));
// Test that the same-type case still works
std::string raw_data = R"([1,2,3])";
std::list<char> data(raw_data.begin(), raw_data.end());
json j2 = json::parse(data.begin(), data.end());
CHECK(j2.at(0) == 1);
}
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
// JSON_HAS_CPP_20 (do not remove; see note at top of file)
TEST_CASE("Parse with std::counted_iterator and std::default_sentinel_t")
{
using iterator_type = std::string::const_iterator;
const std::string json_str = R"({"key":"value","array":[1,2,3]})";
const auto len = static_cast<std::iter_difference_t<iterator_type>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
const json j = json::parse(first, std::default_sentinel);
CHECK(j["key"] == "value");
CHECK(j["array"].size() == 3);
const std::counted_iterator<iterator_type> first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
TEST_CASE("std::counted_iterator reaches the contiguous fast paths")
{
// A sized sentinel makes the remaining element count computable in O(1), so
// std::counted_iterator over a contiguous iterator must reach the same bulk
// string/number scanners as a plain pointer - not just the byte-at-a-time
// fallback (see #5268 for the equivalent memcpy fast path).
#if JSON_HAS_RANGES
// JSON_HAS_RANGES is 0 on standard libraries with an incomplete <ranges>
// (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back
// to the byte-at-a-time scanner; everything below still has to work there.
using adapter_type = nlohmann::detail::iterator_input_adapter<std::counted_iterator<const char*>, std::default_sentinel_t>;
CHECK(adapter_type::supports_bulk_scan);
CHECK(adapter_type::supports_seek);
#endif
// exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and
// integer/floating-point numbers
const std::string json_str =
R"({"ascii":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",)"
"\"utf8\":\"\xe4\xb8\xad\xe6\x96\x87\xf0\x9f\x98\x80\xc3\xa9\","
R"("escaped":"aéb\n\\","ints":[0,-1,18446744073709551615,-9223372036854775808],)"
R"("floats":[1.5,-2.25e3,0.30000000000000004]})";
const auto len = static_cast<std::iter_difference_t<const char*>>(json_str.size());
const std::counted_iterator<const char*> first(json_str.data(), len);
const json j = json::parse(first, std::default_sentinel);
// parsing through the pointer adapter must give exactly the same result
CHECK(j == json::parse(json_str));
#if !defined(JSON_NOEXCEPTION)
// Diagnostics that quote the offending token are reconstructed from the
// already-consumed input (supports_seek), a path a sized sentinel only
// reaches now; check a few that include the "last read" text. Parsing
// invalid input aborts when exceptions are off, hence the guard.
// Raw strings and explicit bytes: an escaped literal and two literals
// written next to each other both read as mistakes to static analysis.
const auto byte = [](int value)
{
return std::string(1, static_cast<char>(value));
};
const std::vector<std::string> diagnostic_docs =
{
"1\nx",
"truX",
"[tru]",
R"("abc)",
R"(["\ud834"])",
R"(["a)" + byte(0x01) + R"(b"])",
R"([")" + byte(0xC3) + byte(0x28) + R"("])",
"[1e]",
R"(["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX)"
};
for (const auto& text : diagnostic_docs)
{
CAPTURE(text);
const std::counted_iterator<const char*> it(text.data(), static_cast<std::iter_difference_t<const char*>>(text.size()));
std::string counted_message;
std::string string_message;
try
{
const json counted_result = json::parse(it, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_message = e.what();
}
try
{
const json string_result = json::parse(text);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_message = e.what();
}
CHECK_FALSE(counted_message.empty());
CHECK(counted_message == string_message);
}
// and errors must still be reported identically
const std::string bad = "[01\n]";
const std::counted_iterator<const char*> bad_first(bad.data(), static_cast<std::iter_difference_t<const char*>>(bad.size()));
std::string counted_what;
std::string string_what;
try
{
const json counted_result = json::parse(bad_first, std::default_sentinel);
static_cast<void>(counted_result);
}
catch (const json::parse_error& e)
{
counted_what = e.what();
}
try
{
const json string_result = json::parse(bad);
static_cast<void>(string_result);
}
catch (const json::parse_error& e)
{
string_what = e.what();
}
CHECK_FALSE(counted_what.empty());
CHECK(counted_what == string_what);
#endif
}
#if !defined(JSON_NOEXCEPTION)
// several cases below are truncated on purpose, and parsing invalid input
// aborts when exceptions are off
TEST_CASE("std::counted_iterator bulk scanning stops at the counted end")
{
// The count, not the size of the underlying buffer, is the end of the
// input: the bulk scanners must never look at the bytes behind it, even
// though they are readable. Each case is compared against parsing the
// equivalent prefix as a std::string.
const auto via_counted = [](const std::string & buf, std::size_t n) -> std::string
{
const std::counted_iterator<const char*> first(buf.data(), static_cast<std::iter_difference_t<const char*>>(n));
try
{
const json j = json::parse(first, std::default_sentinel);
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
const auto via_prefix = [](const std::string & buf, std::size_t n) -> std::string
{
try
{
const json j = json::parse(buf.substr(0, n));
return "OK|" + j.dump();
}
catch (const json::parse_error& e)
{
return {e.what()};
}
};
struct testcase // NOLINT(cppcoreguidelines-pro-type-member-init,hicpp-member-init)
{
const char* buffer;
std::size_t count;
};
const std::vector<testcase> cases =
{
{"[\"abc\"]____TRAILING____", 7}, // exact fit, tail hidden
{"[\"abcdefghijklmnop\"]____", 8}, // cut inside a string
{"[\"abc\"]____", 6}, // cut just before the closing quote
{"[12345]xxxxx", 4}, // cut inside a number
{"[123]999999", 5}, // number ends exactly at the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 12}, // closing quote only behind the count
{"[\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"]", 19}, // cut inside an 8-byte SWAR stride
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]", 5}, // cut inside a UTF-8 sequence
{"[\"\xe4\xb8\xad\xe6\x96\x87\"]____", 10}, // complete UTF-8, tail hidden
{"[1.25e3]TRAILINGDIGITS999", 7}, // number token reaches the count
};
for (const auto& tc : cases)
{
CAPTURE(tc.buffer);
CAPTURE(tc.count);
const std::string buffer = tc.buffer;
CHECK(via_counted(buffer, tc.count) == via_prefix(buffer, tc.count));
}
}
#endif
#endif
} // namespace } // namespace
+1 -43
View File
@@ -53,27 +53,6 @@ TEST_CASE("wide strings")
std::wstring const w = L"\"\xDBFF"; std::wstring const w = L"\"\xDBFF";
json _; json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// the exact message depends on the width of wchar_t: a 16-bit
// wchar_t passes the lone surrogate to the UTF-8 decoder unchanged
// (rejected as a single ill-formed byte at column 2), while a
// 32-bit wchar_t first encodes it as an ill-formed three-byte
// sequence (rejected one byte later, at column 3)
const char* const error_low_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xB0'";
const char* const error_high_surrogate = sizeof(wchar_t) == 2
? "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'"
: "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xED\xA0'";
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'"'}), error_low_surrogate, json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xD800), L'a', L'"'}), error_high_surrogate, json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::wstring{L'"', static_cast<wchar_t>(0xDC00), L'a', L'"'}), error_low_surrogate, json::parse_error&);
} }
} }
@@ -89,22 +68,11 @@ TEST_CASE("wide strings")
SECTION("invalid std::u16string") SECTION("invalid std::u16string")
{ {
if (u16string_is_utf16()) if (wstring_is_utf16())
{ {
std::u16string const w = u"\"\xDBFF"; std::u16string const w = u"\"\xDBFF";
json _; json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a lone low surrogate cannot start a pair
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a high surrogate followed by a non-low-surrogate unit is invalid
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xD800, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a lone low surrogate must not swallow the following unit: pairing
// it with any second unit would produce valid UTF-8, so the error
// has to report an ill-formed byte at the surrogate's own position
CHECK_THROWS_WITH_AS(_ = json::parse(std::u16string{u'"', 0xDC00, u'a', u'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"<U+0000>'", json::parse_error&);
// a valid surrogate pair is still decoded (U+1F600)
CHECK(json::parse(std::u16string{u'"', 0xD83D, 0xDE00, u'"'}).get<std::string>() == "\xF0\x9F\x98\x80");
} }
} }
@@ -125,16 +93,6 @@ TEST_CASE("wide strings")
std::u32string const w = U"\"\x110000"; std::u32string const w = U"\"\x110000";
json _; json _;
CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&);
// a code unit above U+10FFFF must not be narrowed onto the EOF
// sentinel: 0xFFFFFFFF would otherwise end the document silently and
// let everything following it pass the strict end-of-input check
std::u32string const trailing{U'[', U'1', U']', static_cast<char32_t>(0xFFFFFFFF), U'x'};
CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&);
CHECK(!json::accept(trailing));
// the same unit inside a string is reported as an ill-formed byte
CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast<char32_t>(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&);
} }
} }
} }