Compare commits

..
Author SHA1 Message Date
Niels Lohmann 0e03ecee10 Extend memcpy fast path to sized sentinels (e.g. std::counted_iterator)
Extend the memcpy fast path in iterator_input_adapter to sized
sentinels of a different type, not just same-type iterator pairs.
std::counted_iterator paired with std::default_sentinel_t already
satisfies std::contiguous_iterator and std::sized_sentinel_for, so
std::ranges::distance (C++20) lets that combination reach the fast
path too, instead of silently falling back to the byte-by-byte path.

- iterator_is_contiguous now also allows std::sized_sentinel_for<SentinelType,
  IteratorType> under C++20, gated the same way as the existing
  std::contiguous_iterator detection.
- get_elements_impl's fast path uses std::ranges::distance under C++20
  (works for both same-type and sized-sentinel pairs) and falls back to
  std::distance pre-C++20, where SentinelType is always IteratorType.
- Add a C++20-only test exercising json::parse/accept with
  std::counted_iterator + std::default_sentinel_t.
- Document std::default_sentinel_t + std::counted_iterator as a
  SentinelType example across parse.md, accept.md, sax_parse.md, and
  the five from_*.md pages, replacing an earlier ambiguously worded
  bullet.

Addresses review feedback: https://github.com/nlohmann/json/pull/5265#discussion_r3564237584

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 16:26:09 +02:00
Niels Lohmann d23806a327 Fix clang-tidy misc-const-correctness in heterogeneous sentinel test
json_str is only read via .data()/.size() and never reassigned, so
clang-tidy correctly flags it as const-able. Verified against the exact
CI job (silkeh/clang:dev, ci_clang_tidy target) by running clang-tidy
directly on this file plus the five binary-format sentinel tests
touched by prior commits; all are now clean.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 10:06:11 +02:00
Niels Lohmann fa9741f0ce Fix clang-tidy hicpp-named-parameter and misc-const-correctness
- Drop the unused reversed-order operator!= overload from
  utils::istreambuf_sentinel (only iterator != sentinel is ever
  evaluated) and name the remaining friend's sentinel parameter, fixing
  hicpp-named-parameter/readability-named-parameter.
- Mark the istreambuf_iterator first/last helper variable const in the
  five binary-format sentinel tests, fixing misc-const-correctness.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-11 00:10:23 +02:00
Niels Lohmann da95d1184e Fix -Wunneeded-internal-declaration for CustomSentinel in test
CustomSentinel lives in an anonymous namespace (internal linkage), and
the library's parse loop only ever evaluates the iterator-first
direction (it != last), so the reversed-order friend operator!= was
never referenced. Clang's -Weverything flags such unused internal
declarations as an error. Drop the unused overload; the used direction
is enough to satisfy can_compare_ne's either-order detection.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 23:25:21 +02:00
Niels Lohmann 146e5e0bc7 Merge iterator+sentinel overloads and fix ambiguity/CI issues
Address PR review feedback and CI failures:

- Merge the separate same-type and sentinel-type iterator overloads of
  parse(), accept(), sax_parse(), and the five from_* binary deserializers
  into a single overload with SentinelType defaulted to IteratorType,
  as suggested in review. Applied the same simplification to the
  detail::input_adapter() free functions.
- Fix a latent ambiguity: some compilers (e.g. GCC 4.8) unreliably SFINAE
  the operator!= detection for std::nullptr_t against container/string
  types, making calls like parse(s, nullptr, ...) ambiguous with the
  compatible-input overload. can_compare_ne now explicitly excludes
  std::nullptr_t as a SentinelType.
- Use a named enable_if_t template parameter instead of an unnamed
  function parameter for the SFINAE guard, fixing a clang-tidy
  hicpp-named-parameter/readability-named-parameter failure.
- Update parse.md, accept.md, sax_parse.md, and the five from_*.md pages
  to document the merged overload instead of separate (2)/(3) overloads,
  also fixing an over-160-char line that broke the documentation
  style_check CI job.
- Rework the BSON iterator+sentinel test to parse a BSON file already
  present in the test suite instead of writing/deleting a temp file.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 23:15:17 +02:00
Niels Lohmann 2269656bc6 Add iterator+sentinel tests and docs for binary deserializers
This commit extends the C++20 ranges support (iterator+sentinel pairs) to the
binary format deserializers from_cbor, from_msgpack, from_ubjson, from_bjdata,
and from_bson, matching what was already done for parse(), accept(), and
sax_parse().

Changes:
- Add istreambuf_sentinel helper to test_utils.hpp for EOF detection in tests
- Add 5 new test cases that read binary files directly via
  std::istreambuf_iterator<char> + sentinel, without pre-buffering
- Update documentation for all 5 from_* functions to document overload (3)
  with SentinelType parameter
- All tests pass; verified against existing test suite data
- Fix potential buffer over-read warning in heterogeneous iterator test

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-10 19:50:04 +02:00
59 changed files with 281 additions and 2452 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.
## 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
- Explore security-related topics and contribute to tools and projects through
+2 -2
View File
@@ -39,14 +39,14 @@ jobs:
egress-policy: audit
- name: Checkout pull request
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: main
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
- name: Checkout tools
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: tools
ref: develop
+4 -4
View File
@@ -32,20 +32,20 @@ jobs:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
languages: c-cpp
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
egress-policy: audit
- name: 'Checkout Repository'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: 'Dependency Review'
+2 -2
View File
@@ -32,7 +32,7 @@ jobs:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -43,6 +43,6 @@ jobs:
output: 'flawfinder_results.sarif'
- name: Upload analysis results to GitHub Security tab
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+3 -3
View File
@@ -26,7 +26,7 @@ jobs:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run CMake
@@ -45,7 +45,7 @@ jobs:
DEVELOPER_DIR: /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run CMake
@@ -62,7 +62,7 @@ jobs:
standard: [11, 14, 17, 20, 23, 26]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run CMake
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install virtual environment
run: make install_venv -C docs/mkdocs
+3 -3
View File
@@ -41,12 +41,12 @@ jobs:
egress-policy: audit
- name: "Checkout code"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
@@ -76,6 +76,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: results.sarif
+2 -2
View File
@@ -37,7 +37,7 @@ jobs:
egress-policy: audit
# Checkout project source
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
@@ -61,7 +61,7 @@ jobs:
# Upload SARIF file generated in previous step
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: semgrep.sarif
if: always()
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
stale-issue-label: 'state: stale'
stale-pr-label: 'state: stale'
+36 -44
View File
@@ -21,11 +21,11 @@ jobs:
runs-on: ubuntu-latest
container: gcc:latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -43,11 +43,11 @@ jobs:
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
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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -66,11 +66,11 @@ jobs:
- name: Install 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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -85,11 +85,11 @@ jobs:
steps:
- name: Install git, clang-tools, iwyu (ci_single_binaries), and 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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -104,11 +104,11 @@ jobs:
steps:
- name: Install build-essential
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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -122,7 +122,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install dependencies and de_DE locale
@@ -142,7 +142,7 @@ jobs:
name: code-coverage-report
path: ${{ github.workspace }}/build/html
- name: Publish report to Coveralls
uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ${{ github.workspace }}/build/json.info.filtered.noexcept
@@ -163,15 +163,7 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
apt-get update
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,
# 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
add-apt-repository -y ppa:ubuntu-toolchain-r/test
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/ xenial main"
@@ -180,11 +172,11 @@ jobs:
apt-add-repository -y "deb http://archive.ubuntu.com/ubuntu/ xenial-updates universe"
apt-get update
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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: CXX=g++-${{ matrix.compiler }} cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -198,11 +190,11 @@ jobs:
compiler: ['7', '8', '9', '10', '11', '12', '13', '14', '15', 'latest']
container: gcc:${{ matrix.compiler }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -215,11 +207,11 @@ jobs:
compiler: ['3.4', '3.5', '3.6', '3.7', '3.8', '3.9', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15-bullseye', '16', '17', '18', '19', '20', 'latest']
container: silkeh/clang:${{ matrix.compiler }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Set env FORCE_STDCPPFS_FLAG for clang 7 / 8 / 9 / 10
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' }}
@@ -235,11 +227,11 @@ jobs:
matrix:
standard: [11, 14, 17, 20, 23, 26]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -255,11 +247,11 @@ jobs:
steps:
- name: Install git and 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:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build with libc++
@@ -282,11 +274,11 @@ jobs:
cuda: ['11.8.0', '12.1.1', '12.6.3']
container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -299,14 +291,14 @@ jobs:
runs-on: ubuntu-latest
container: ${{ matrix.container }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# The module test uses `import std;`, which needs CMake's experimental
# import-std support. Its opt-in token is CMake-version-specific, so pin
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
- name: Get pinned CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
with:
cmakeVersion: 4.3.4
# 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.
container: intel/oneapi-hpckit:2023.2.1-devel-ubuntu22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -345,9 +337,9 @@ jobs:
runs-on: ubuntu-latest
container: intel/oneapi-hpckit:latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -357,9 +349,9 @@ jobs:
runs-on: ubuntu-latest
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DJSON_CI=On
- name: Build
@@ -375,11 +367,11 @@ jobs:
- name: Install emscripten
uses: mymindstorm/setup-emsdk@4528d102f7230f0e7b276855c01ea1159be0e984 # v16
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Run CMake
run: cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -GNinja
- name: Build
@@ -396,7 +388,7 @@ jobs:
with:
egress-policy: audit
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run CMake
+9 -9
View File
@@ -24,7 +24,7 @@ jobs:
architecture: [x64, x86]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set up MinGW
@@ -49,7 +49,7 @@ jobs:
runs-on: windows-2022
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Set extra CXX_FLAGS for latest std_version
@@ -86,9 +86,9 @@ jobs:
runs-on: windows-2025
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Get latest CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
- name: Set extra CXX_FLAGS for latest std_version
# /wd5285 silences C5285 emitted by the bundled third-party doctest.h, which
# specializes std::tuple (newly diagnosed by the VS2026 v145 toolset)
@@ -122,7 +122,7 @@ jobs:
runs-on: windows-11-arm
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Run CMake (Release)
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'
@@ -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]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Clang
@@ -173,7 +173,7 @@ jobs:
architecture: [Win32, x64]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Run CMake
@@ -186,14 +186,14 @@ jobs:
ci_module_cpp20:
runs-on: windows-2022
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
# The module test uses `import std;`, which needs CMake's experimental
# import-std support. Its opt-in token is CMake-version-specific, so pin
# CMake to the version whose token is set in tests/module_cpp20/CMakeLists.txt.
- name: Get pinned CMake and ninja
uses: lukka/get-cmake@e6906078ebd1ccb8ce51ab4626ac46a1b5a517e3 # v4.4.0
uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4
with:
cmakeVersion: 4.3.4
- name: Run CMake (Debug)
+3 -11
View File
@@ -90,6 +90,7 @@ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/nl
- [Steve Sperandeo](https://github.com/homer6)
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
- [Steve Wagner](https://github.com/ciroque)
- [Lion Yang](https://github.com/LionNatsu)
### Further support
@@ -1801,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
- [**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
- [**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
- [**CMake**](https://cmake.org) for build automation
- [**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)
- [**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
- [**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
@@ -1822,15 +1823,6 @@ The library itself consists of a single header file licensed under the MIT licen
## 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
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-padded We do not care about padding warnings.
# -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
# (to_chars, serializer, lexer, binary reader/writer, input
# adapters, json_pointer) plus vendored Doctest itself (~208
@@ -23,6 +20,5 @@ set(CLANG_CXXFLAGS
-Wno-extra-semi-stmt
-Wno-padded
-Wno-covered-switch-default
-Wno-c2y-extensions
-Wno-unsafe-buffer-usage
)
+3 -34
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`
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:
> Major type 2: A byte string. The number of bytes in the string is equal to the argument.
[CBOR's RFC 7049](https://tools.ietf.org/html/rfc7049) describes this type as:
> 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
family](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family) describes this type as:
@@ -50,38 +51,6 @@ represent a byte array in modern C++.
The default values for `BinaryType` is `#!cpp std::vector<std::uint8_t>`.
#### Custom BinaryType behavior
When a custom `BinaryType` is configured (other than the default `#!cpp std::vector<std::uint8_t>`), you can assign
values of that type directly to a `basic_json` instance, and they will automatically be recognized as binary values
rather than arrays:
```cpp
using custom_json = nlohmann::basic_json<
nlohmann::ordered_map, // ObjectType
std::vector, // ArrayType
std::string, // StringType
bool, // BooleanType
std::int64_t, // NumberIntegerType
std::uint64_t, // NumberUnsignedType
double, // NumberFloatType
std::allocator, // AllocatorType
nlohmann::adl_serializer,
std::vector<std::byte> // Custom BinaryType
>;
std::vector<std::byte> data{std::byte{1}, std::byte{2}, std::byte{3}};
custom_json j = data; // Creates a binary value, not an array
assert(j.is_binary());
// Round-tripping works seamlessly
auto extracted = j.get<std::vector<std::byte>>();
assert(extracted == data);
```
This automatic type detection is a convenience feature that only applies to custom (non-default) `BinaryType` configurations.
The default `nlohmann::json` continues to treat `#!cpp std::vector<std::uint8_t>` as arrays for backward compatibility.
#### Storage
Binary Arrays are stored as pointers in a `basic_json` type. That is, for any access to array values, a pointer of the
-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
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
Linear.
@@ -29,14 +29,7 @@ Discarding a value (i.e., returning `#!cpp false`) has different effects dependi
called:
- 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
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.
read.
- 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.
@@ -56,7 +49,7 @@ called:
## Return value
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
@@ -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"
```
??? 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
- [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
- 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.
+1 -4
View File
@@ -69,8 +69,7 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
[`input_format_t`](input_format_t.md) for more information
`strict` (in)
: whether the input has to be consumed completely (optional, `#!cpp true` by default); when `#!cpp false` and the
input is a `#!cpp std::istream`, the stream is left positioned right after the parsed value
: whether the input has to be consumed completely (optional, `#!cpp true` by default)
`ignore_comments` (in)
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
@@ -137,8 +136,6 @@ A UTF-8 byte order mark is silently ignored.
- 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 overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
- Changed in version 4.0.0 to leave a `#!cpp std::istream` positioned right after the parsed value when `strict` is
`#!cpp false`; see [`operator>>`](../operator_gtgt.md#notes).
!!! 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"`
- 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)"`
- 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
@@ -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
[`JSON_Diagnostics`](../../integration/cmake.md#json_diagnostics) (`OFF` by default)
which defines `JSON_DIAGNOSTICS` accordingly. Note this only applies when building the
library from source — see the pre-installed-package caveat on that page.
which defines `JSON_DIAGNOSTICS` accordingly.
## Examples
+5 -19
View File
@@ -37,25 +37,13 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
repeatedly to read a sequence of concatenated JSON values from the same stream:
```cpp
std::istringstream input("1true[2]");
json j1, j2, j3;
input >> j1; // j1 == 1, stream now positioned right after it
input >> j2; // j2 == true
input >> j3; // j3 == [2]
json j1, j2;
input >> j1; // parses the first value, stream now positioned right after it
input >> j2; // parses the next value
```
!!! note "Changed behavior for numbers"
A number is the only value whose end can be detected solely by reading the character that follows it. Up to
version 3.13.0 that character was consumed and not put back, so the stream was left one byte too far whenever a
number was immediately followed by another value: reading `1true` yielded `1` and left the stream at `rue`.
Values had to be separated by whitespace to work around this.
The terminating character is now only looked at and left in the stream, so no separator is required. Code that
relied on the extra byte being swallowed will observe it again.
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.
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.
!!! warning "Deprecation"
@@ -87,5 +75,3 @@ Note that reading concatenated values does **not** work for [JSON Lines](../feat
## Version history
- Added in version 1.0.0.
- Changed in version 4.0.0 to leave the character that terminates a number in the stream, so that the stream is
positioned right after the parsed value for every value type.
-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
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
??? example
-42
View File
@@ -56,48 +56,6 @@ std::equal_to<> // since C++14
- **find**
- **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
??? example
@@ -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
@@ -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.
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
```cpp
@@ -95,8 +82,8 @@ The library maps BSON record types to JSON value types as follows:
!!! note "Handling of BSON type 0x11"
This library deserializes BSON type `0x11` (Timestamp) as a `number_unsigned` value. The 64-bit value is preserved,
but the Timestamp type information is not.
BSON type 0x11 is used to represent uint64 numbers. This library treats these values purely as uint64 numbers
and does not parse them into date-related formats.
??? 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 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
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 |
|-----------------|--------------------------------------------|-----------------------------------|------------|
+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}}
```
`#!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
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()`
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:
`#!cpp std::pair<A, B>`, `#!cpp std::tuple<Ts...>`, `#!cpp std::array<T, N>`, C arrays `#!cpp T[N]`, and
`#!cpp std::map`/`#!cpp std::unordered_map` with a non-string key type (which is read from an array of
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.
Converting a JSON array to `#!cpp std::array<T, N>` does not check that the JSON array's size matches `N`:
if the JSON array is longer, the extra elements are silently dropped; if it is shorter, the remaining
`std::array` elements are left default-constructed. No exception is thrown in either case.
```cpp
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 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
auto a = j.get<std::array<int, 3>>(); // {1, 2, 3} -- elements 4 and 5 silently dropped
```
If a size mismatch is an error in your application, check the size yourself before converting.
## 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
@@ -181,20 +136,6 @@ std::vector<int> 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
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
@@ -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)).
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
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`
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
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.
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
[notes](../../api/operator_gtgt.md#notes) for details.
`operator>>` does work -- see its [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
When parsing a [binary format](../features/binary_formats/index.md), the byte vector ends before the complete value has
been read.
When parsing CBOR or MessagePack, the byte vector ends before the complete value has 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
@@ -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 8: syntax error while parsing BSON number: unexpected end of input
```
### 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 5: syntax error while parsing BSON document: document size 6 does not match the number of bytes read (5)
```
### 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
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
While parsing a map key, a value that is not a string has been read.
!!! 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 3: syntax error while parsing BJData string: string length must not be negative
```
### json.exception.parse_error.114
@@ -864,21 +853,13 @@ and this exception no longer occurs.
### 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 `#` for [UBJSON](../features/binary_formats/ubjson.md)/[BJData](../features/binary_formats/bjdata.md),
or the encoded length for [CBOR](../features/binary_formats/cbor.md).
The size (following `#`) of an UBJSON array or object exceeds the maximal capacity.
!!! failure "Example messages"
!!! failure "Example message"
```
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
@@ -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.
### 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
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.
### 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`
!!! 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)
- [Robert Jefe Lindstädt](https://github.com/eljefedelrodeodeljefe)
- [Steve Wagner](https://github.com/ciroque)
- [Lion Yang](https://github.com/LionNatsu)
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.
!!! 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`
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
[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
There is no official package published to the [ESP-IDF Component Registry](https://components.espressif.com) or the
+1 -1
View File
@@ -2,7 +2,7 @@ wheel==0.47.0
mkdocs==1.6.1 # documentation framework
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-minify-plugin==0.8.0 # plugin "minify"
mkdocs-redirects==1.2.3 # plugin "redirects"
@@ -19,7 +19,6 @@
#include <unordered_map> // unordered_map
#include <utility> // pair, declval
#include <valarray> // valarray
#include <vector> // vector
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/macro_scope.hpp>
@@ -333,7 +332,6 @@ template < typename BasicJsonType, typename ConstructibleArrayType,
!is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&
!std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&
!is_compatible_binary_type<BasicJsonType, ConstructibleArrayType>::value&&
!is_basic_json<ConstructibleArrayType>::value,
int > = 0 >
auto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)
@@ -379,25 +377,6 @@ inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t&
bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void from_json(const BasicJsonType& j, CompatibleArrayType& bin)
{
if (j.is_binary())
{
bin = static_cast<CompatibleArrayType>(*j.template get_ptr<const typename BasicJsonType::binary_t*>());
}
else if (j.is_array())
{
from_json_array_impl(j, bin, priority_tag<3> {});
}
else
{
JSON_THROW(type_error::create(302, concat("type must be binary or array, but is ", j.type_name()), &j));
}
}
template<typename BasicJsonType, typename ConstructibleObjectType,
enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>
inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)
@@ -177,11 +177,8 @@ struct external_constructor<value_t::array>
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
, int > = 0 >
enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,
int > = 0 >
static void construct(BasicJsonType& j, const CompatibleArrayType& arr)
{
using std::begin;
@@ -221,25 +218,6 @@ struct external_constructor<value_t::array>
j.set_parents();
j.assert_invariant();
}
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType,
enable_if_t<is_compatible_range_view<std::remove_cvref_t<CompatibleArrayType>>::value, int> = 0>
static void construct(BasicJsonType& j, CompatibleArrayType && arr)
{
j.m_data.m_value.destroy(j.m_data.m_type);
j.m_data.m_type = value_t::array;
j.m_data.m_value = value_t::array;
for (auto&& x : std::forward<CompatibleArrayType>(arr))
{
j.m_data.m_value.array->push_back(x);
j.set_parent(j.m_data.m_value.array->back());
}
j.assert_invariant();
}
#endif
};
template<>
@@ -377,44 +355,19 @@ template < typename BasicJsonType, typename CompatibleArrayType,
!is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&
!is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&
!std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&
!is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value&&
!is_basic_json<CompatibleArrayType>::value
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
,
!is_basic_json<CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)
{
external_constructor<value_t::array>::construct(j, arr);
}
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template < typename BasicJsonType, typename T,
enable_if_t < is_compatible_range_view<std::remove_cvref_t<T>>::value
&& !is_compatible_string_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_compatible_object_type<BasicJsonType, std::remove_cvref_t<T>>::value
&& !is_basic_json<std::remove_cvref_t<T>>::value, int > = 0 >
inline void to_json(BasicJsonType& j, T && arr)
{
external_constructor<value_t::array>::construct(j, std::forward<T>(arr));
}
#endif
template<typename BasicJsonType>
inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)
{
external_constructor<value_t::binary>::construct(j, bin);
}
template < typename BasicJsonType, typename CompatibleArrayType,
enable_if_t < is_compatible_binary_type<BasicJsonType, CompatibleArrayType>::value,
int > = 0 >
inline void to_json(BasicJsonType& j, const CompatibleArrayType& bin)
{
external_constructor<value_t::binary>::construct(j, typename BasicJsonType::binary_t(bin));
}
template<typename BasicJsonType, typename T,
enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>
inline void to_json(BasicJsonType& j, const std::valarray<T>& arr)
+27 -161
View File
@@ -163,44 +163,14 @@ class binary_reader
// 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.
@return whether a valid BSON-value was passed to the SAX parser
*/
bool parse_bson_internal()
{
const std::size_t document_start = chars_read;
std::int32_t document_size{};
if (!get_number<std::int32_t, true>(input_format_t::bson, document_size))
{
return false;
}
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
@@ -212,11 +182,6 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(document_start, document_size)))
{
return false;
}
return sax->end_object();
}
@@ -290,10 +255,7 @@ class binary_reader
// All BSON binary values have a subtype
std::uint8_t subtype{};
if (JSON_HEDLEY_UNLIKELY(!get_number<std::uint8_t>(input_format_t::bson, subtype)))
{
return false;
}
get_number<std::uint8_t>(input_format_t::bson, subtype);
result.set_subtype(subtype);
return get_binary(input_format_t::bson, len, result);
@@ -346,8 +308,7 @@ class binary_reader
case 0x08: // boolean
{
std::uint8_t value{};
return get_number<std::uint8_t>(input_format_t::bson, value) && sax->boolean(value != 0);
return sax->boolean(get() != 0);
}
case 0x0A: // null
@@ -436,12 +397,8 @@ class binary_reader
*/
bool parse_bson_array()
{
const std::size_t document_start = chars_read;
std::int32_t document_size{};
if (!get_number<std::int32_t, true>(input_format_t::bson, document_size))
{
return false;
}
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
@@ -453,11 +410,6 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(!check_bson_document_size(document_start, document_size)))
{
return false;
}
return sax->end_array();
}
@@ -704,15 +656,13 @@ class binary_reader
case 0x9A: // array (four-byte uint32_t for n follow)
{
std::uint32_t len{};
std::size_t size{};
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "array") && get_cbor_array(size, tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);
}
case 0x9B: // array (eight-byte uint64_t for n follow)
{
std::uint64_t len{};
std::size_t size{};
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "array") && get_cbor_array(size, tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);
}
case 0x9F: // array (indefinite length)
@@ -760,15 +710,13 @@ class binary_reader
case 0xBA: // map (four-byte uint32_t for n follow)
{
std::uint32_t len{};
std::size_t size{};
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "map") && get_cbor_object(size, tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);
}
case 0xBB: // map (eight-byte uint64_t for n follow)
{
std::uint64_t len{};
std::size_t size{};
return get_number(input_format_t::cbor, len) && get_cbor_container_size(len, size, "map") && get_cbor_object(size, tag_handler);
return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);
}
case 0xBF: // map (indefinite length)
@@ -811,37 +759,25 @@ class binary_reader
case 0xD8:
{
std::uint8_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore))
{
return false;
}
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xD9:
{
std::uint16_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore))
{
return false;
}
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDA:
{
std::uint32_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore))
{
return false;
}
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
case 0xDB:
{
std::uint64_t subtype_to_ignore{};
if (!get_number(input_format_t::cbor, subtype_to_ignore))
{
return false;
}
get_number(input_format_t::cbor, subtype_to_ignore);
break;
}
default:
@@ -859,40 +795,28 @@ class binary_reader
case 0xD8:
{
std::uint8_t subtype{};
if (!get_number(input_format_t::cbor, subtype))
{
return false;
}
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xD9:
{
std::uint16_t subtype{};
if (!get_number(input_format_t::cbor, subtype))
{
return false;
}
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDA:
{
std::uint32_t subtype{};
if (!get_number(input_format_t::cbor, subtype))
{
return false;
}
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
case 0xDB:
{
std::uint64_t subtype{};
if (!get_number(input_format_t::cbor, subtype))
{
return false;
}
get_number(input_format_t::cbor, subtype);
b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));
break;
}
@@ -934,7 +858,7 @@ class binary_reader
const auto byte1 = static_cast<unsigned char>(byte1_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
// to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very
@@ -947,8 +871,8 @@ class binary_reader
{
const int exp = (half >> 10u) & 0x1Fu;
const unsigned int mant = half & 0x3FFu;
JSON_ASSERT(exp <= 31);
JSON_ASSERT(mant <= 1023);
JSON_ASSERT(0 <= exp&& exp <= 32);
JSON_ASSERT(mant <= 1024);
switch (exp)
{
case 0:
@@ -1183,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
array of indefinite size
@@ -1947,29 +1846,6 @@ class binary_reader
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
@@ -2007,25 +1883,25 @@ class binary_reader
case 'i':
{
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':
{
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':
{
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':
{
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':
@@ -2547,7 +2423,7 @@ class binary_reader
const auto byte1 = static_cast<unsigned char>(byte1_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
// to IEEE 754 in 2008, today's programming platforms often
// still only have limited support for them. It is very
@@ -2560,8 +2436,8 @@ class binary_reader
{
const int exp = (half >> 10u) & 0x1Fu;
const unsigned int mant = half & 0x3FFu;
JSON_ASSERT(exp <= 31);
JSON_ASSERT(mant <= 1023);
JSON_ASSERT(0 <= exp&& exp <= 32);
JSON_ASSERT(mant <= 1024);
switch (exp)
{
case 0:
@@ -2878,17 +2754,7 @@ class binary_reader
case token_type::value_unsigned:
return sax->number_unsigned(number_lexer.get_number_unsigned());
case token_type::value_float:
{
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));
}
return sax->number_float(number_lexer.get_number_float(), std::move(number_string));
case token_type::uninitialized:
case token_type::literal_true:
case token_type::literal_false:
@@ -101,9 +101,6 @@ class input_stream_adapter
// maintain ifstream flags, except eof
if (is != nullptr)
{
// consume the character last returned by get_character() unless it
// was given back with release_lookahead()
commit_lookahead();
is->clear(is->rdstate() & std::ios::eofbit);
}
}
@@ -118,60 +115,29 @@ class input_stream_adapter
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
input_stream_adapter(input_stream_adapter&& rhs) noexcept
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
: is(rhs.is), sb(rhs.sb)
{
rhs.is = nullptr;
rhs.sb = nullptr;
rhs.lookahead = false;
}
// Whether the character last returned by get_character() can be given back
// to the input with release_lookahead().
static constexpr bool supports_lookahead = true;
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
// end up as the same value, e.g., 0xFFFFFFFF.
//
// The character is peeked rather than consumed: it is only stepped over
// once the next character is requested, or when the adapter is destroyed.
// Until then, release_lookahead() can leave it in the input.
std::char_traits<char>::int_type get_character()
{
if (lookahead)
{
// step over the character returned by the previous call
sb->sbumpc();
}
auto res = sb->sgetc();
auto res = sb->sbumpc();
// set eof manually, as we don't use the istream interface.
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
{
// there is nothing to step over next time
lookahead = false;
is->clear(is->rdstate() | std::ios::eofbit);
}
else
{
lookahead = true;
}
return res;
}
// Leave the character last returned by get_character() in the input, so
// that the next read from the stream - by this adapter or by the caller
// once parsing is done - sees it again. Unlike putting a consumed
// character back, this cannot fail.
void release_lookahead() noexcept
{
lookahead = false;
}
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
commit_lookahead();
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
{
@@ -181,23 +147,9 @@ class input_stream_adapter
}
private:
// Step over the character last returned by get_character(). The character
// has already been peeked successfully, so for every streambuf with a get
// area this is a pointer increment that cannot fail.
void commit_lookahead()
{
if (lookahead)
{
lookahead = false;
sb->sbumpc();
}
}
/// the associated input stream
std::istream* is = nullptr;
std::streambuf* sb = nullptr;
/// whether get_character() peeked a character that is not consumed yet
bool lookahead = false;
};
#endif // JSON_NO_IO
@@ -443,30 +395,17 @@ struct wide_string_input_helper<BaseInputAdapter, 2>
}
else
{
// A supplementary code point is a high surrogate (0xD800..0xDBFF)
// 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()))
if (JSON_HEDLEY_UNLIKELY(!input.empty()))
{
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));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
valid_pair = true;
}
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));
utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));
utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));
utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));
utf8_bytes_filled = 4;
}
if (!valid_pair)
else
{
utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);
utf8_bytes_filled = 1;
+11 -40
View File
@@ -626,7 +626,14 @@ class json_sax_dom_callback_parser
if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())
{
// 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;
@@ -667,9 +674,8 @@ class json_sax_dom_callback_parser
bool end_array()
{
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());
if (keep)
@@ -703,19 +709,9 @@ class json_sax_dom_callback_parser
keep_stack.pop_back();
// 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();
}
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());
}
ref_stack.back()->m_data.m_value.array->pop_back();
}
return true;
@@ -805,19 +801,6 @@ class json_sax_dom_callback_parser
}
#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] skip_callback whether we should skip calling the callback
@@ -858,18 +841,6 @@ class json_sax_dom_callback_parser
// do not handle this value if we just learnt it shall be discarded
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};
}
-62
View File
@@ -125,24 +125,6 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
return false;
}
// Detect whether an input adapter reads with one character of lookahead that
// can be left in the input (see input_stream_adapter::supports_lookahead),
// detected like supports_seek above.
template<typename InputAdapterType>
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
{
return InputAdapterType::supports_lookahead;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
{
return false;
}
/*!
@brief lexical analysis
@@ -164,12 +146,6 @@ class lexer : public lexer_base<BasicJsonType>
static constexpr bool lazy_token_string =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
/// whether a simulated unget can be passed on to the input adapter, which
/// then leaves the character in the input; see
/// input_adapter_supports_lookahead
static constexpr bool can_release_lookahead =
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
public:
using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -1480,21 +1456,6 @@ scan_number_done:
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
}
/// adapter without lookahead: nothing to do (see release_lookahead)
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
/// adapter with lookahead: leave the character in the input instead
void release_lookahead_impl(std::true_type /*can_release*/)
{
if (next_unget)
{
// the character is read from the input again rather than replayed
// from current, so the adapter must not step over it
next_unget = false;
ia.release_lookahead();
}
}
/// seekable adapter: nothing was captured, so nothing to undo
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
@@ -1558,29 +1519,6 @@ scan_number_done:
return position;
}
/*!
@brief pass a pending simulated unget on to the input
unget() only rewinds the lexer's own bookkeeping, so the character that
terminated the last token (e.g. the character after a number) would still
be stepped over when the input adapter is done. Callers that hand the
input back to the user afterwards - operator>> and non-strict sax_parse -
call this once when scanning is done, so that the input is positioned
right after the value.
Adapters without lookahead (see input_adapter_supports_lookahead) are not
handed back to the user, so this is a no-op for them.
Scanning may continue after this call: @a next_unget is cleared, and the
character is read from the input again instead of being replayed from
@a current. A pending unget of EOF needs no special case, because reaching
EOF leaves no lookahead to release.
*/
void release_lookahead()
{
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
}
/// 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
{
+3 -20
View File
@@ -99,14 +99,8 @@ class parser
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
sax_parse_internal(&sdp);
if (!strict)
{
// the caller keeps using the input: position it right after
// the value by leaving the character that terminated it
m_lexer.release_lookahead();
}
// in strict mode, input must be completely read
else if (get_token() != token_type::end_of_input)
if (strict && (get_token() != token_type::end_of_input))
{
sdp.parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -133,13 +127,8 @@ class parser
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
sax_parse_internal(&sdp);
if (!strict)
{
// see above
m_lexer.release_lookahead();
}
// in strict mode, input must be completely read
else if (get_token() != token_type::end_of_input)
if (strict && (get_token() != token_type::end_of_input))
{
sdp.parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
@@ -176,14 +165,8 @@ class parser
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
const bool result = sax_parse_internal(sax);
if (result && !strict)
{
// the caller keeps using the input: position it right after the
// value by leaving the character that terminated it
m_lexer.release_lookahead();
}
// strict mode: next byte must be EOF
else if (result && strict && (get_token() != token_type::end_of_input))
if (result && strict && (get_token() != token_type::end_of_input))
{
return sax->parse_error(m_lexer.get_position(),
m_lexer.get_token_string(),
+12 -110
View File
@@ -13,15 +13,11 @@
#include <tuple> // tuple
#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type
#include <utility> // declval
#include <vector> // vector
#if defined(__cpp_lib_byte) && __cpp_lib_byte >= 201603L
#include <cstddef> // byte
#endif
#include <nlohmann/detail/iterators/iterator_traits.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#ifdef JSON_HAS_CPP_17
#include <optional> // optional
#endif
#include <nlohmann/detail/meta/call_std/begin.hpp>
#include <nlohmann/detail/meta/call_std/end.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
@@ -231,9 +227,7 @@ struct char_traits<signed char> : std::char_traits<char>
// Redefine to_int_type function
static int_type to_int_type(char_type c) noexcept
{
// cast via unsigned char: sign-extending a negative char_type would make
// byte 0xFF indistinguishable from eof()
return static_cast<int_type>(static_cast<unsigned char>(c));
return static_cast<int_type>(c);
}
static char_type to_char_type(int_type i) noexcept
@@ -456,51 +450,6 @@ struct is_constructible_string_type
value_type_t, laundered_type >>::value;
};
// Forward declarations: iteration_proxy.hpp includes this file, so we cannot
// include it here.
template<typename IteratorType> class iteration_proxy;
template<typename IteratorType> class iteration_proxy_value;
// Identifies nlohmann's internal iteration-proxy types. These must be excluded
// before evaluating any std::ranges concept to avoid circular constraints.
template<typename T> struct is_iteration_proxy_type : std::false_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy<T>> : std::true_type {};
template<typename T> struct is_iteration_proxy_type<iteration_proxy_value<T>> : std::true_type {};
// In C++26, std::optional satisfies std::ranges::view; exclude it so the
// range-view overload does not hijack the optional serializer.
#ifdef JSON_HAS_CPP_17
template<typename T> struct is_range_view_optional_type : std::false_type {};
template<typename T> struct is_range_view_optional_type<std::optional<T>> : std::true_type {};
#else
template<typename T> struct is_range_view_optional_type : std::false_type {};
#endif
// std::ranges does not work properly on MinGW due to incomplete C++20 support
// see https://github.com/nlohmann/json/issues/4916
#if JSON_HAS_RANGES && !defined(__MINGW32__)
// SafeToCheck guards against types that trigger circular constraints when
// std::ranges::view<T> is evaluated on GCC 12 / libstdc++ 12:
// - iteration_proxy / iteration_proxy_value directly
// - views wrapping the above (e.g. owning_view<iteration_proxy<...>>)
// - views wrapping basic_json (e.g. ref_view<json>) — same circularity
// via json's constructors → is_compatible_array_type → here
// nlohmann's plain range_value_t (iterator_traits-based) is safe to call
// before any std::ranges concept is touched, so we use it for the checks.
template < typename T, bool SafeToCheck =
!is_iteration_proxy_type<T>::value &&
!is_iteration_proxy_type<detected_t<range_value_t, T>>::value &&
!is_basic_json<detected_t<range_value_t, T>>::value &&
!is_range_view_optional_type<T>::value >
struct is_compatible_range_view : std::false_type {};
template<typename T>
struct is_compatible_range_view<T, true>
: std::bool_constant<std::ranges::view<T>> {};
#endif
template<typename BasicJsonType, typename CompatibleArrayType, typename = void>
struct is_compatible_array_type_impl : std::false_type {};
@@ -512,38 +461,13 @@ struct is_compatible_array_type_impl <
is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&
// special case for types like std::filesystem::path whose iterator's value_type are themselves
// c.f. https://github.com/nlohmann/json/pull/3073
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value
// When range-view support is enabled, std::ranges::view types (e.g. std::string_view,
// filter_view) can match BOTH this iterator-based specialization AND the view-based one
// below, causing ambiguity. Exclude views here so the two specializations are mutually
// exclusive: this one handles plain iterable containers, the other handles views.
#if JSON_HAS_RANGES && !defined(__MINGW32__)
&& !is_compatible_range_view<CompatibleArrayType>::value
#endif
>>
!std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >>
{
static constexpr bool value =
is_constructible<BasicJsonType,
range_value_t<CompatibleArrayType>>::value;
};
#if JSON_HAS_RANGES && !defined(__MINGW32__)
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type_impl <
BasicJsonType, CompatibleArrayType,
enable_if_t < is_compatible_range_view<CompatibleArrayType>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, char>::value
&& !std::is_same<detected_t<range_value_t, CompatibleArrayType>, wchar_t>::value >>
{
// CompatibleArrayType is a std::ranges::view here, so std::ranges::range_value_t
// is safe and correctly handles C++20 iterators that may lack classic iterator_traits.
static constexpr bool value =
is_constructible<BasicJsonType,
std::ranges::range_value_t<CompatibleArrayType>>::value;
};
#endif
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_array_type
: is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};
@@ -635,14 +559,6 @@ template<typename BasicJsonType, typename CompatibleType>
struct is_compatible_type
: is_compatible_type_impl<BasicJsonType, CompatibleType> {};
template<typename BasicJsonType, typename CompatibleArrayType>
struct is_compatible_binary_type
{
static constexpr bool value =
std::is_same<typename BasicJsonType::binary_t::container_type, CompatibleArrayType>::value &&
!std::is_same<typename BasicJsonType::binary_t::container_type, std::vector<std::uint8_t>>::value;
};
template<typename BasicJsonType, typename CompatibleReferenceType>
struct is_compatible_reference_type_impl
{
@@ -701,34 +617,20 @@ struct is_json_pointer_of<A, ::nlohmann::json_pointer<A>> : std::true_type {};
template <typename A>
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
// We dispatch on is_json_pointer_of as a plain bool (rather than folding it
// 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>
template<typename Compare, typename A, typename B, typename = void>
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>
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>
using detect_is_transparent = typename T::is_transparent;
@@ -979,28 +979,13 @@ class binary_writer
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
*/
void write_bson_entry_header(const string_t& name,
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(
reinterpret_cast<const CharType*>(name.c_str()),
name.size() + 1u);
@@ -1042,7 +1027,7 @@ class binary_writer
{
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(
reinterpret_cast<const CharType*>(value.c_str()),
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
{
@@ -1098,22 +1083,22 @@ class binary_writer
@brief Writes a BSON element with key @a name and unsigned @a value
*/
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_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_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
{
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)
{
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;
@@ -1177,7 +1162,7 @@ class binary_writer
{
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));
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);
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:
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)
{
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)
{
@@ -1662,15 +1647,7 @@ class binary_writer
std::size_t len = (value.at(key).empty() ? 0 : 1);
for (const auto& el : value.at(key))
{
// a dimension is read as an unsigned value below, so anything that
// 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;
}
len *= static_cast<std::size_t>(el.template get<std::uint64_t>());
len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);
}
key = "_ArrayData_";
@@ -1679,24 +1656,6 @@ class binary_writer
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(dtype);
@@ -1710,70 +1669,70 @@ class binary_writer
{
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')
{
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')
{
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')
{
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')
{
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')
{
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')
{
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')
{
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')
{
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')
{
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;
File diff suppressed because it is too large Load Diff
-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());
}
}
}
-78
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);
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);
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'};
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);
}
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)")
{
// create vector with two elements of the same type
@@ -2786,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&);
}
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")
{
// create a single-character string for all number types
-157
View File
@@ -11,35 +11,12 @@
#include <nlohmann/json.hpp>
using nlohmann::json;
#include <cstdint>
#include <fstream>
#include <limits>
#include <sstream>
#include <vector>
#include "make_test_data_available.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")
{
SECTION("individual values not supported")
@@ -103,14 +80,6 @@ TEST_CASE("BSON")
#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")
{
// 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);
}
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")
{
// an MD5 hash
@@ -860,41 +794,6 @@ TEST_CASE("Incomplete BSON Input")
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("key")
@@ -955,62 +854,6 @@ TEST_CASE("Unsupported BSON input")
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")
{
SECTION("number")
+1 -37
View File
@@ -1999,42 +1999,6 @@ TEST_CASE("CBOR regressions")
}
#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())
{
SECTION("input from flynn")
@@ -2345,7 +2309,7 @@ TEST_CASE("all CBOR first bytes")
}
#endif
TEST_CASE("examples from RFC 8949 Appendix A")
TEST_CASE("examples from RFC 7049 Appendix A")
{
SECTION("numbers")
{
-49
View File
@@ -1440,13 +1440,6 @@ TEST_CASE("parser class")
]
)";
const auto* structured_object = R"(
{
"foo": [1, 2],
"bar": 3
}
)";
SECTION("filter nothing")
{
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}));
}
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("first closing event")
-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&);
CHECK_THROWS_WITH_AS(std::optional<int>(j_null),
"[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")
-197
View File
@@ -14,15 +14,10 @@ using nlohmann::json;
using namespace nlohmann::literals; // NOLINT(google-build-using-namespace)
#endif
#include <cstddef>
#include <iostream>
#include <iterator>
#include <sstream>
#include <streambuf>
#include <string>
#include <utility>
#include <valarray>
#include <vector>
#if defined(_WIN32)
#define NOMINMAX
@@ -224,58 +219,6 @@ class proxy_iterator
iterator* m_it = nullptr;
};
// A streambuf that keeps no get area at all and refuses every putback: with an
// empty get area, sungetc() always ends up in pbackfail(). Used to check that
// the character terminating a number is left in the input without relying on
// the streambuf being able to put a consumed character back.
class no_putback_streambuf : public std::streambuf
{
public:
explicit no_putback_streambuf(std::string s) : m_data(std::move(s)) {}
protected:
// peek at the next character without consuming it
int_type underflow() override
{
if (m_pos >= m_data.size())
{
return traits_type::eof();
}
return traits_type::to_int_type(m_data[m_pos]);
}
// consume the next character
int_type uflow() override
{
if (m_pos >= m_data.size())
{
return traits_type::eof();
}
return traits_type::to_int_type(m_data[m_pos++]);
}
int_type pbackfail(int_type /*c*/) override
{
return traits_type::eof();
}
private:
std::string m_data;
std::size_t m_pos = 0;
};
// read the characters that are left in a stream
std::string remaining(std::istream& is)
{
std::string result;
char c = 0;
while (is.get(c))
{
result += c;
}
return result;
}
// JSON_HAS_CPP_20
#if defined(__cpp_char8_t)
bool check_utf8()
@@ -484,30 +427,6 @@ TEST_CASE("deserialization")
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")
{
std::array<uint8_t, 5> const v { {'t', 'r', 'u', 'e'} };
@@ -1238,122 +1157,6 @@ TEST_CASE("deserialization")
}
}
SECTION("stream position after extraction (#5340)")
{
SECTION("a number does not consume the character that terminates it")
{
// a number is only terminated by the character following it; that
// character must be given back so the stream is positioned right
// after the value
const std::vector<std::pair<std::string, std::string>> tests =
{
{"1true", "true"},
{"1[2]", "[2]"},
{"1{}", "{}"},
{R"(1"a")", R"("a")"},
{"1 true", " true"},
{"12,", ","},
{"-0.5e3x", "x"},
{"1null", "null"}
};
for (const auto& test : tests)
{
CAPTURE(test.first);
std::istringstream ss(test.first);
json j;
ss >> j;
CHECK(j == json::parse(test.first.substr(0, test.first.size() - test.second.size())));
CHECK(remaining(ss) == test.second);
}
}
SECTION("values that are self-delimiting are unaffected")
{
const std::vector<std::pair<std::string, std::string>> tests =
{
{"truefalse", "false"},
{"[1][2]", "[2]"},
{R"({"a":1}{"b":2})", R"({"b":2})"},
{R"("a""b")", R"("b")"},
{"null null", " null"}
};
for (const auto& test : tests)
{
CAPTURE(test.first);
std::istringstream ss(test.first);
json j;
ss >> j;
CHECK(remaining(ss) == test.second);
}
}
SECTION("a number at the end of the input leaves nothing behind")
{
for (const std::string s :
{"1", "12", "-3.5e2", " 7 "
})
{
CAPTURE(s);
std::istringstream ss(s);
json j;
ss >> j;
CHECK(remaining(ss).find_first_not_of(" \t\n\r") == std::string::npos);
}
}
SECTION("repeated extraction of concatenated values")
{
std::istringstream ss(R"(1true[2]3"x"{"a":4}5)");
const std::vector<json> expected =
{
json(1), json(true), json::parse("[2]"), json(3),
json("x"), json::parse(R"({"a":4})"), json(5)
};
for (const auto& e : expected)
{
json j;
ss >> j;
CHECK(j == e);
}
}
SECTION("sax_parse with strict == false")
{
std::istringstream ss("1true");
SaxEventLogger l;
CHECK(json::sax_parse(ss, &l, nlohmann::detail::input_format_t::json, false));
CHECK(l.events.size() == 1);
CHECK(l.events[0] == "number_unsigned(1)");
CHECK(remaining(ss) == "true");
}
SECTION("strict parsing still rejects trailing data")
{
std::istringstream ss("1true");
json _;
CHECK_THROWS_WITH_AS(_ = json::parse(ss),
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected true literal; expected end of input", json::parse_error&);
std::istringstream ss2("1true");
CHECK_FALSE(json::accept(ss2));
}
SECTION("a streambuf that cannot put back is not needed")
{
// the terminating character is never consumed, so no putback
// position is required
no_putback_streambuf buf("1true");
std::istream is(&buf);
json j;
is >> j;
CHECK(j == json(1));
CHECK(remaining(is) == "true");
}
}
// build with C++20
// JSON_HAS_CPP_20
#if defined(__cpp_char8_t)
-99
View File
@@ -1136,40 +1136,6 @@ TEST_CASE("regression tests 2")
CHECK((decoded == json_4804::array()));
}
SECTION("discussion #4209 - custom BinaryType direct assignment and round-tripping")
{
// Test that assigning a custom BinaryType directly creates a binary value, not an array
const std::vector<std::byte> original{std::byte{1}, std::byte{2}, std::byte{3}};
const json_4804 j = original;
CHECK(j.is_binary());
CHECK(!j.is_array());
// Test round-tripping: extracting the binary value back as the custom container type
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted == original);
// Test that the default json alias behavior is unchanged: std::vector<uint8_t> -> array
const json default_json = std::vector<std::uint8_t> {1, 2, 3};
CHECK(default_json.is_array());
CHECK(!default_json.is_binary());
}
SECTION("discussion #4209 - custom BinaryType extraction from parsed array")
{
// Test that extracting a custom BinaryType from a parsed JSON array still works
// (not just from a binary-typed node)
const auto j = json_4804::parse("[1,2,3]");
CHECK(j.is_array());
CHECK(!j.is_binary());
// Extracting as custom BinaryType should work from arrays
const auto extracted = j.get<std::vector<std::byte>>();
CHECK(extracted.size() == 3);
CHECK(extracted[0] == std::byte{1});
CHECK(extracted[1] == std::byte{2});
CHECK(extracted[2] == std::byte{3});
}
SECTION("issue #5046 - implicit conversion of return json to std::optional no longer implicit")
{
const json jval{};
@@ -1199,46 +1165,6 @@ TEST_CASE("regression tests 2")
}
#endif
#if JSON_HAS_RANGES && !defined(__MINGW32__)
SECTION("issue #4916 - constructing array from C++20 ranges view does not work")
{
std::vector<int> nums{1, 2, 37, 42, 21};
auto filteredNums = nums | std::views::filter([](int i)
{
return i > 10;
});
json const j(filteredNums);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
// owning_view is not available in libstdc++ < 12
#if JSON_HAS_RANGES && !defined(__MINGW32__) && !(defined(__GLIBCXX__) && _GLIBCXX_RELEASE < 12)
SECTION("issue #4916 - constructing array from prvalue C++20 ranges view (owning_view)")
{
json const j(std::vector<int> {1, 2, 37, 42, 21} | std::views::filter([](int i)
{
return i > 10;
}));
CHECK(j.type() == json::value_t::array);
CHECK(j == json({37, 42, 21}));
}
#endif
#if JSON_HAS_RANGES && !defined(__MINGW32__)
SECTION("issue #4916 - constructing array from C++20 transform view (prvalue elements)")
{
std::vector<int> nums{1, 2, 3};
auto t = nums | std::views::transform([](int i) noexcept
{
return i * 2;
});
json const j(t);
CHECK(j.type() == json::value_t::array);
CHECK(j == json({2, 4, 6}));
}
#endif
}
TEST_CASE_TEMPLATE("issue #4798 - nlohmann::json::to_msgpack() encode float NaN as double", T, double, float) // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
@@ -1530,29 +1456,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}}));
}
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
-30
View File
@@ -53,34 +53,4 @@ TEST_CASE("type traits")
// 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());
}
}
}
}
-27
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);
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);
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'};
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);
}
@@ -1864,31 +1862,6 @@ TEST_CASE("UBJSON")
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&);
}
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")
+3 -4
View File
@@ -216,16 +216,15 @@ TEST_CASE("Parse with heterogeneous iterator and sentinel types")
// 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 auto len = static_cast<std::iter_difference_t<std::string::const_iterator>>(json_str.size());
const std::counted_iterator<iterator_type> first(json_str.begin(), len);
std::counted_iterator 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);
std::counted_iterator first2(json_str.begin(), len);
CHECK(json::accept(first2, std::default_sentinel));
}
#endif
+1 -33
View File
@@ -53,27 +53,6 @@ TEST_CASE("wide strings")
std::wstring const w = L"\"\xDBFF";
json _;
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")
{
if (u16string_is_utf16())
if (wstring_is_utf16())
{
std::u16string const w = u"\"\xDBFF";
json _;
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");
}
}