Compare commits

..
Author SHA1 Message Date
Niels Lohmann 282f84cc8e ⬆️ Doctest 2.5.3
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-08 11:04:03 +02:00
dependabot[bot] 3b0dd69928 ⬆️ Bump step-security/harden-runner from 2.19.4 to 2.20.0 (#5242) 2026-07-07 20:01:40 +02:00
Daniel Falk c05c5e229b Add missing copyright notices to SBOM (#5241) 2026-07-07 20:00:49 +02:00
Niels Lohmann acf076a677 Fix ADL leak of nlohmann::detail through basic_json's default base class (#5238) 2026-07-06 12:47:24 +02:00
Paul Dreik 33edc9751c fix unit-algorithms test reliance on implementation specific behaviour (#5236)
the standard only specifies that the first elements are sorted.

this caused my experimental C++ standard library implementation to fail.

Signed-off-by: Paul Dreik <github@pauldreik.se>
2026-07-06 08:25:30 +02:00
Niels Lohmann 83c87cb9e0 Read binary strings/blobs in bulk chunks with a memcpy fast path (#5233) 2026-07-05 19:26:02 +02:00
Niels LohmannandClaude Opus 4.8 eed1587000 Reconstruct lexer diagnostics lazily for seekable input (#120) (#5234)
`lexer::get()` copied every scanned character into `token_string` on the
whole successful-parse hot path, yet that buffer is consumed only by
`get_token_string()` when rendering the "last read" fragment of a parse
error. On well-formed input the per-byte copy (plus the `unget()` pop)
is pure overhead that is always discarded.

For seekable input adapters - random-access, single-byte iterators such
as those backing `std::string`, `const char*`, and `std::vector<char>` -
the offending token is now reconstructed on demand from the input when
an error is reported, using a saved start offset, and the eager copy is
skipped. Streaming adapters (file, istream, wide-string, and user-defined
adapters) keep the eager copy; the strategy is chosen at compile time via
`input_adapter_supports_seek`, so adapters without the capability are
unaffected.

Error messages are byte-for-byte identical across all adapters, verified
by a new parity regression test. Microbenchmark (4 MB mixed JSON, parsed
from a std::string): ~149 -> ~160 MB/s, about +8%.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 10:42:48 +02:00
Niels Lohmann c034480c22 📝 add more docs (#5231)
Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-04 11:13:25 +02:00
Niels LohmannandClaude Opus 4.8 899cf31255 Harden CI: validate PR artifact inputs, migrate off deprecated Semgrep action (#5232)
* Harden CI workflows: validate PR artifact inputs and migrate off deprecated Semgrep action

Address two CI/supply-chain hardening items from the 2026-07-03 security
audit:

- comment_check_amalgamation.yml (todo 117): the privileged `workflow_run`
  job consumes an untrusted PR artifact. Validate `author` against a strict
  GitHub-username pattern and `number` as a positive integer before use, and
  extract the artifact into a dedicated directory (`unzip -o pr.zip -d
  ./pr_artifact`), reading only the two expected files by fixed path. This
  prevents Markdown/mention injection via the attacker-controlled `author`
  text and avoids a malicious archive touching the workspace.

- semgrep.yml (todo 118): `returntocorp/semgrep-action` is deprecated (the
  org was renamed to `semgrep/*`). Replace it with an explicit `semgrep ci`
  invocation via the maintained CLI; the deployment is inferred from
  SEMGREP_APP_TOKEN.

Todo 116 (CIFuzz `@master` refs) already carries a comment documenting the
OSS-Fuzz-recommended exception, so no change is needed there.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix Semgrep step: use `semgrep scan` instead of token-gated `semgrep ci`

The CI `Scan` job failed with "Path does not exist: semgrep.sarif" because
`semgrep ci` requires a login token (SEMGREP_APP_TOKEN), which this repo does
not have configured, so it bailed without producing a SARIF file. The former
returntocorp/semgrep-action, given no token, fell back to plain
`semgrep scan --sarif`; match that with `semgrep scan --config auto`, which
needs no token and always produces the SARIF for upload.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 11:10:02 +02:00
Niels Lohmann c363dc3e4d Bump codeql-action to v4.36.3 and group future updates (#5230)
Bumps init/analyze/autobuild together (previously split across #5226,
#5227, #5228, which each failed CI due to a version mismatch between
the CodeQL config and the running action). Also adds a dependabot
group so future codeql-action bumps land in a single PR.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-07-03 08:04:41 +02:00
Niels LohmannandClaude Opus 4.8 972f5cc10b Attach a ready-to-apply patch when the amalgamation check fails (#5229)
When a PR is not amalgamated/formatted, the astyle version friction (see
the recurring blocker across many PRs) means contributors often struggle
to reproduce the exact fix locally. The check now regenerates the
amalgamation and formatting, captures the difference as a patch, and
uploads it as the `amalgamation-patch` artifact. The failure comment
links to that artifact and tells contributors to run
`git apply amalgamation.patch`, so they no longer need to install the
pinned astyle version themselves.

The pass/fail verdict is unchanged: the same PRs fail as before, and a
correctly amalgamated PR uploads nothing and passes.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 23:05:00 +02:00
dependabot[bot] 518c5c887a ⬆️ Bump github/codeql-action/upload-sarif from 4.36.2 to 4.36.3 (#5225)
Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.36.2 to 4.36.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/8aad20d150bbac5944a9f9d289da16a4b0d87c1e...54f647b7e1bb85c95cddabcd46b0c578ec92bc1a)

---
updated-dependencies:
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.36.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-02 23:04:05 +02:00
66 changed files with 8567 additions and 5289 deletions
+4
View File
@@ -4,6 +4,10 @@ updates:
directory: /
schedule:
interval: daily
groups:
codeql-action:
patterns:
- "github/codeql-action/*"
- package-ecosystem: pip
directory: /docs/mkdocs
+34 -14
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -57,22 +57,42 @@ jobs:
python3 -mvenv venv
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
- name: Check amalgamation
- name: Regenerate amalgamation and formatting
run: |
cd $MAIN_DIR
rm -fr $INCLUDE_DIR/json.hpp~ $INCLUDE_DIR/json_fwd.hpp~
cp $INCLUDE_DIR/json.hpp $INCLUDE_DIR/json.hpp~
cp $INCLUDE_DIR/json_fwd.hpp $INCLUDE_DIR/json_fwd.hpp~
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json.json -s .
python3 $TOOL_DIR/amalgamate.py -c $TOOL_DIR/config_json_fwd.json -s .
echo "Format (1)"
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet $INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
diff $INCLUDE_DIR/json.hpp~ $INCLUDE_DIR/json.hpp
diff $INCLUDE_DIR/json_fwd.hpp~ $INCLUDE_DIR/json_fwd.hpp
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=orig $(find docs/examples include tests -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
echo Check
find $MAIN_DIR -name '*.orig' -exec false {} \+
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
$(find docs/examples include tests -type f \( -name '*.hpp' -o -name '*.cpp' -o -name '*.cu' \) -not -path 'tests/thirdparty/*' -not -path 'tests/abi/include/nlohmann/*' | sort)
- name: Build patch and check for differences
id: diff
run: |
cd $MAIN_DIR
mkdir -p ${{ github.workspace }}/patch
git diff --patch --no-color > ${{ github.workspace }}/patch/amalgamation.patch
if [ -s ${{ github.workspace }}/patch/amalgamation.patch ]; then
echo "The source code has not been amalgamated/formatted correctly. Diff:"
cat ${{ github.workspace }}/patch/amalgamation.patch
echo "has_diff=true" >> "$GITHUB_OUTPUT"
else
echo "has_diff=false" >> "$GITHUB_OUTPUT"
fi
# Uploaded so contributors can fix their PR with `git apply amalgamation.patch`
# instead of installing the pinned astyle version locally.
- name: Upload patch
if: steps.diff.outputs.has_diff == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: amalgamation-patch
path: patch/amalgamation.patch
- name: Fail if not amalgamated/formatted
if: steps.diff.outputs.has_diff == 'true'
run: exit 1
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+3 -3
View File
@@ -27,7 +27,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -38,7 +38,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
languages: c-cpp
@@ -48,4 +48,4 @@ jobs:
uses: github/codeql-action/autobuild@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
@@ -19,11 +19,12 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: 'Download artifact'
id: download
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
@@ -43,7 +44,13 @@ jobs:
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
- run: unzip pr.zip
var hasPatch = artifacts.data.artifacts.some((artifact) => artifact.name == "amalgamation-patch");
core.setOutput('has_patch', String(hasPatch));
# Extract the untrusted PR artifact into a dedicated empty directory and
# read only the two expected files by fixed path afterwards. This avoids a
# malicious archive overwriting workspace files or escaping via ../ paths.
- run: unzip -o pr.zip -d ./pr_artifact
- name: 'Comment on PR'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -51,8 +58,19 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var fs = require('fs');
const author = fs.readFileSync('./author')
const issue_number = Number(fs.readFileSync('./number'));
// Both values come from a fork-triggered workflow and are therefore
// attacker-controlled. Validate them strictly before use to prevent
// Markdown/mention injection and bogus REST API filters.
const author = fs.readFileSync('./pr_artifact/author', 'utf8').trim();
if (!/^[A-Za-z0-9-]{1,39}$/.test(author)) {
core.setFailed(`Refusing to proceed: untrusted author value '${author}' is not a valid GitHub username.`);
return;
}
const issue_number = Number(fs.readFileSync('./pr_artifact/number', 'utf8').trim());
if (!Number.isInteger(issue_number) || issue_number <= 0) {
core.setFailed('Refusing to proceed: untrusted PR number is not a positive integer.');
return;
}
const opts = github.rest.issues.listForRepo.endpoint.merge({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -70,12 +88,20 @@ jobs:
break
}
}
const hasPatch = '${{ steps.download.outputs.has_patch }}' === 'true';
const runUrl = '${{ github.event.workflow_run.html_url }}';
await github.rest.issues.createComment({
issue_number: issue_number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated.'
+ (first ? ' @' + author + ' Please read and follow the [Contribution Guidelines]'
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly.'
+ (hasPatch ? '\n\n📎 A ready-to-apply patch is attached to the [failed workflow run](' + runUrl + ') as the `amalgamation-patch` artifact.'
+ ' Download it, then apply it locally from the repository root with:'
+ '\n\n```shell\ngit apply amalgamation.patch\n```\n\n'
+ 'This does not require installing astyle yourself.'
: '')
+ (first ? '\n\n@' + author + ' Please read and follow the [Contribution Guidelines]'
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#files-to-change).'
: '')
})
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+2 -2
View File
@@ -27,7 +27,7 @@ jobs:
security-events: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -43,6 +43,6 @@ jobs:
output: 'flawfinder_results.sarif'
- name: Upload analysis results to GitHub Security tab
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+2 -2
View File
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -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@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: results.sarif
+19 -8
View File
@@ -32,7 +32,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -41,16 +41,27 @@ jobs:
with:
persist-credentials: false
# Scan code using project's configuration on https://semgrep.dev/manage
- uses: returntocorp/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d
with:
publishToken: ${{ secrets.SEMGREP_APP_TOKEN }}
publishDeployment: ${{ secrets.SEMGREP_DEPLOYMENT_ID }}
generateSarif: "1"
# The former returntocorp/semgrep-action is deprecated (the org was renamed
# to semgrep/*); the maintained approach is to install the CLI and invoke
# it directly. We use `semgrep scan` (not `semgrep ci`, which requires a
# login token): with no SEMGREP_APP_TOKEN configured this is exactly what
# the old action fell back to, running community rules with no token.
# SEMGREP_APP_TOKEN is still passed through so registry auth works if a
# token is ever added.
- name: Install Semgrep
run: python3 -m pip install --user semgrep
# `semgrep scan --sarif` always exits 0 even with findings; continue-on-error
# is a safety net so the SARIF upload still runs if the scan itself errors.
- name: Run Semgrep
run: semgrep scan --config auto --sarif --output=semgrep.sarif
continue-on-error: true
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
# Upload SARIF file generated in previous step
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
uses: github/codeql-action/upload-sarif@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
with:
sarif_file: semgrep.sarif
if: always()
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+4 -4
View File
@@ -50,7 +50,7 @@ jobs:
target: [ci_test_amalgamation, ci_test_single_header, ci_cppcheck, ci_cpplint, ci_reproducible_tests, ci_non_git_tests, ci_offline_testdata, ci_reuse_compliance, ci_test_valgrind]
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -108,7 +108,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -316,7 +316,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
@@ -339,7 +339,7 @@ jobs:
target: [ci_test_examples, ci_test_build_documentation]
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
+2 -4
View File
@@ -90,13 +90,11 @@ jobs:
- name: Get latest CMake and ninja
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)
run: |
if [ "${{ matrix.std_version }}" = "latest" ]; then
echo "flags=/permissive- /std:c++latest /utf-8 /W4 /WX /wd5285" >> $GITHUB_ENV
echo "flags=/permissive- /std:c++latest /utf-8 /W4 /WX" >> $GITHUB_ENV
else
echo "flags=/W4 /WX /wd5285" >> $GITHUB_ENV
echo "flags=/W4 /WX" >> $GITHUB_ENV
fi
shell: bash
- name: Run CMake (Release)
+4
View File
@@ -11,6 +11,10 @@ Files: include/nlohmann/thirdparty/hedley.hpp
Copyright: 2016-2021 Evan Nemerson <evan@nemerson.com>
License: CC0
Files: include/nlohmann/detail/meta/cpp_future.hpp
Copyright: 2013-2026 Niels Lohmann <https://nlohmann.me> and 2018 The Abseil Authors
License: MIT AND Apache-2.0
Files: tests/thirdparty/doctest/*
Copyright: 2016-2023 Viktor Kirilov
License: MIT
+1 -1
View File
@@ -5,7 +5,7 @@
# -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-unsafe-buffer-usage Otherwise Doctest would not compile.
# -Wno-unsafe-buffer-usage Otherwise library code (strlen) would not compile.
# -Wno-missing-noreturn We found no way to silence this warning otherwise, see PR #4871
set(CLANG_CXXFLAGS
-2
View File
@@ -4,7 +4,6 @@
# -Wno-aggregate-return The library uses aggregate returns.
# -Wno-long-long The library uses the long long type to interface with system functions.
# -Wno-namespaces The library uses namespaces.
# -Wno-nrvo Doctest triggers this warning.
# -Wno-padded We do not care about padding warnings.
# -Wno-system-headers We do not care about warnings in system headers.
# -Wno-templates The library uses templates.
@@ -232,7 +231,6 @@ set(GCC_CXXFLAGS
-Wnonnull
-Wnonnull-compare
-Wnormalized=nfkc
-Wno-nrvo
-Wnull-dereference
-Wodr
-Wold-style-cast
+1
View File
@@ -54,6 +54,7 @@ This function is only needed to express two edge cases that cannot be realized w
- [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list
- [`object`](object.md) - create a JSON object value from an initializer list
- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values
## Version history
+1
View File
@@ -75,6 +75,7 @@ Binary values are serialized as an object containing two keys:
- [to_string](to_string.md) returns a string representation of a JSON value
- [operator<<](../operator_ltlt.md) serialize to stream
- [Serialization](../../features/serialization.md) - the serialization article
## Version history
@@ -64,6 +64,7 @@ Logarithmic in the size of the container, O(log(`size()`)).
- [emplace_back](emplace_back.md) add a value to an array
- [insert](insert.md) add values to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history
@@ -58,6 +58,7 @@ Amortized constant.
- [operator+=](operator+=.md) add a value to an array/object
- [push_back](push_back.md) add a value to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history
+1
View File
@@ -206,6 +206,7 @@ Strong exception safety: if an exception occurs, the original value stays intact
- [clear](clear.md) clears the contents
- [insert](insert.md) add values to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history
@@ -88,6 +88,7 @@ std::string format_as(const BasicJsonType& j)
- [dump](dump.md)
- [std::formatter<basic_json>](std_formatter.md) - the `std::format` (C++20) equivalent
- [Serialization](../../features/serialization.md) - the serialization article
## Version history
+1
View File
@@ -155,6 +155,7 @@ overload (3).
- [get_ptr](get_ptr.md) get a pointer to the stored value
- [get_ref](get_ref.md) get a reference to the stored value
- [operator ValueType](operator_ValueType.md) get a value via implicit conversion
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history
@@ -62,6 +62,7 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
- [get](get.md) get a value (explicit conversion)
- [get_ref](get_ref.md) get a reference to the stored value
- [get_ptr](get_ptr.md) get a pointer to the stored value
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history
@@ -57,6 +57,7 @@ the initializer list constructor `basic_json(initializer_list_t, bool, value_t)`
- [`basic_json(initializer_list_t)`](basic_json.md) - create a JSON value from an initializer list
- [`array`](array.md) - create a JSON array value from an initializer list
- [Creating JSON values](../../features/creating_values.md) - the article on creating JSON values
## Version history
@@ -78,6 +78,7 @@ Linear in the size of the JSON value.
## See also
- [get](get.md) get a value (explicit conversion)
- [Converting values](../../features/conversions.md) - the type conversions article
## Version history
@@ -115,6 +115,7 @@ invalidates all iterators and all references.
- [emplace_back](emplace_back.md) add a value to an array
- [operator+=](operator+=.md) add a value to an array/object
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history
@@ -50,6 +50,7 @@ provides `<format>`, controlled by the [`JSON_HAS_STD_FORMAT`](../macros/json_ha
- [dump](dump.md) - serialization
- [operator<<(std::ostream&)](../operator_ltlt.md) - serialize to stream
- [format_as](format_as.md) - customization point used by `fmt::format` (fmtlib)
- [Serialization](../../features/serialization.md) - the serialization article
## Version history
@@ -59,6 +59,7 @@ std::string to_string(const BasicJsonType& j)
## See also
- [dump](dump.md)
- [Serialization](../../features/serialization.md) - the serialization article
## Version history
@@ -149,6 +149,7 @@ Basic guarantee: if an exception is thrown during the operation, the JSON value
- [insert](insert.md) add values to an array/object
- [merge_patch](merge_patch.md) applies a JSON Merge Patch
- [Modifying values](../../features/modifying_values.md) - the article on modifying values
## Version history
@@ -56,6 +56,10 @@ Linear.
--8<-- "examples/operator_literal_json.output"
```
## See also
- [Creating JSON values](../features/creating_values.md) - the article on creating JSON values
## Version history
- Added in version 1.0.0.
+6
View File
@@ -80,6 +80,12 @@ Linear.
```json
--8<-- "examples/operator_ltlt__json_pointer.output"
```
## See also
- [dump](basic_json/dump.md) - serialize to a JSON-formatted string
- [Serialization](../features/serialization.md) - the serialization article
## Version history
1. Added in version 1.0.0. Added support for indentation character and deprecated
+2 -2
View File
@@ -85,7 +85,7 @@ Some important things:
If you just want to serialize/deserialize some structs, the `to_json`/`from_json` functions can be a lot of boilerplate.
There are several macros to make your life easier as long as you want to use a JSON object as serialization. The macros are following the naming pattern, and you can chose the macro based on the needed features:
There are several macros to make your life easier as long as you want to use a JSON object as serialization. The macros are following the naming pattern, and you can choose the macro based on the needed features:
- All the macros start with `NLOHMANN_DEFINE`.
- If you want a macro for the derived object, use the [`DERIVED_TYPE`](../api/macros/nlohmann_define_derived_type.md) variant, otherwise use `TYPE`.
@@ -139,7 +139,7 @@ For _derived_ classes and structs, use the following macros
```cpp
namespace ns {
struct person_derived : person {
std:string email;
std::string email;
};
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(person_derived, person, email)
+1 -1
View File
@@ -11,7 +11,7 @@ Runtime assertions can be switched off by defining the preprocessor macro `NDEBU
## Change assertion behavior
The behavior of runtime assertions can be changes by defining macro [`JSON_ASSERT(x)`](../api/macros/json_assert.md)
The behavior of runtime assertions can be changed by defining macro [`JSON_ASSERT(x)`](../api/macros/json_assert.md)
before including the `json.hpp` header.
## Function with runtime assertions
@@ -123,7 +123,7 @@ The library uses the following mapping from JSON values types to BJData types ac
The current version of this library does not yet support automatic detection of and conversion from a nested JSON
array input to a BJData ND-array.
[JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/master/JData_specification.md#annotated-storage-of-n-d-arrays)
[JDataAAFmt]: https://github.com/NeuroJSON/jdata/blob/master/JData_specification.md#annotated-storage-of-n-d-arrays
!!! info "Restrictions in optimized data types for arrays and objects"
@@ -146,7 +146,7 @@ The library uses the following mapping from JSON values types to BJData types ac
suggested by the BJData documentation. In particular, this means that the serialization and the deserialization of
JSON containing binary values into BJData and back will result in a different JSON object.
[BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array)
[BJDataBinArr]: https://github.com/NeuroJSON/bjdata/blob/master/Binary_JData_Specification.md#optimized-binary-array
??? example
@@ -52,28 +52,29 @@ The library uses the following mapping from JSON values types to BSON types:
The library maps BSON record types to JSON value types as follows:
| BSON type | BSON marker byte | JSON value type |
|-----------------------|------------------|-----------------|
| double | 0x01 | number_float |
| string | 0x02 | string |
| document | 0x03 | object |
| array | 0x04 | array |
| binary | 0x05 | binary |
| undefined | 0x06 | *unsupported* |
| ObjectId | 0x07 | *unsupported* |
| boolean | 0x08 | boolean |
| UTC Date-Time | 0x09 | *unsupported* |
| null | 0x0A | null |
| Regular Expr. | 0x0B | *unsupported* |
| DB Pointer | 0x0C | *unsupported* |
| JavaScript Code | 0x0D | *unsupported* |
| Symbol | 0x0E | *unsupported* |
| JavaScript Code | 0x0F | *unsupported* |
| int32 | 0x10 | number_integer |
| uint64(Timestamp) | 0x11 | number_unsigned |
| 128-bit decimal float | 0x13 | *unsupported* |
| Max Key | 0x7F | *unsupported* |
| Min Key | 0xFF | *unsupported* |
| BSON type | BSON marker byte | JSON value type |
|--------------------------|------------------|-----------------|
| double | 0x01 | number_float |
| string | 0x02 | string |
| document | 0x03 | object |
| array | 0x04 | array |
| binary | 0x05 | binary |
| undefined | 0x06 | *unsupported* |
| ObjectId | 0x07 | *unsupported* |
| boolean | 0x08 | boolean |
| UTC Date-Time | 0x09 | *unsupported* |
| null | 0x0A | null |
| Regular Expr. | 0x0B | *unsupported* |
| DB Pointer | 0x0C | *unsupported* |
| JavaScript Code | 0x0D | *unsupported* |
| Symbol | 0x0E | *unsupported* |
| JavaScript Code w/ scope | 0x0F | *unsupported* |
| int32 | 0x10 | number_integer |
| uint64(Timestamp) | 0x11 | number_unsigned |
| int64 | 0x12 | number_integer |
| 128-bit decimal float | 0x13 | *unsupported* |
| Max Key | 0x7F | *unsupported* |
| Min Key | 0xFF | *unsupported* |
!!! warning "Incomplete mapping"
@@ -5,7 +5,7 @@ extremely small code sizes, fairly small message size, and extensibility without
!!! abstract "References"
- [CBOR Website](http://cbor.io) - the main source on CBOR
- [CBOR Website](http://cbor.io) - the main source on CBOR
- [CBOR Playground](http://cbor.me) - an interactive webpage to translate between JSON and CBOR
- [RFC 7049](https://tools.ietf.org/html/rfc7049) - the CBOR specification
@@ -37,22 +37,22 @@ The library uses the following mapping from JSON values types to CBOR types acco
| number_float | *any value representable by a float* | Single-Precision Float | 0xFA |
| number_float | *any value NOT representable by a float* | Double-Precision Float | 0xFB |
| string | *length*: 0..23 | UTF-8 string | 0x60..0x77 |
| string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 |
| string | *length*: 24..255 | UTF-8 string (1 byte follow) | 0x78 |
| string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 |
| string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7A |
| string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7B |
| array | *size*: 0..23 | array | 0x80..0x97 |
| array | *size*: 23..255 | array (1 byte follow) | 0x98 |
| array | *size*: 24..255 | array (1 byte follow) | 0x98 |
| array | *size*: 256..65535 | array (2 bytes follow) | 0x99 |
| array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9A |
| array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9B |
| object | *size*: 0..23 | map | 0xA0..0xB7 |
| object | *size*: 23..255 | map (1 byte follow) | 0xB8 |
| object | *size*: 24..255 | map (1 byte follow) | 0xB8 |
| object | *size*: 256..65535 | map (2 bytes follow) | 0xB9 |
| object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xBA |
| object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xBB |
| binary | *size*: 0..23 | byte string | 0x40..0x57 |
| binary | *size*: 23..255 | byte string (1 byte follow) | 0x58 |
| binary | *size*: 24..255 | byte string (1 byte follow) | 0x58 |
| binary | *size*: 256..65535 | byte string (2 bytes follow) | 0x59 |
| binary | *size*: 65536..4294967295 | byte string (4 bytes follow) | 0x5A |
| binary | *size*: 4294967296..18446744073709551615 | byte string (8 bytes follow) | 0x5B |
@@ -41,7 +41,7 @@ See [binary values](../binary_values.md) for more information.
|--------------------|-------------|--------------|-------------------|---------------|
| BJData | 53.2 % | 91.1 % | 78.1 % | 96.6 % |
| BJData (size) | 58.6 % | 92.1 % | 86.7 % | 97.4 % |
| BJData (size+tyoe) | 58.6 % | 92.1 % | 86.5 % | 97.4 % |
| BJData (size+type) | 58.6 % | 92.1 % | 86.5 % | 97.4 % |
| BSON | 85.8 % | 95.2 % | 95.8 % | 106.7 % |
| CBOR | 50.5 % | 86.3 % | 68.4 % | 88.0 % |
| MessagePack | 50.5 % | 86.0 % | 68.5 % | 87.9 % |
+9 -9
View File
@@ -41,7 +41,7 @@ binary.has_subtype(); // returns false
binary_with_subtype.has_subtype(); // returns true
binary_with_subtype.clear_subtype();
binary_with_subtype.has_subtype(); // returns true
binary_with_subtype.has_subtype(); // returns false
binary_with_subtype.set_subtype(42);
binary.set_subtype(23);
@@ -146,7 +146,7 @@ as an array of uint8 values. The library implements this translation.
auto v = json::to_bjdata(j);
```
`v` is a `std::vector<std::uint8t>` with the following 20 elements:
`v` is a `std::vector<std::uint8_t>` with the following 20 elements:
```c
0x7B // '{'
@@ -158,10 +158,10 @@ as an array of uint8 values. The library implements this translation.
0x7D // '}'
```
The following code uses the type and size optimization for UBJSON:
The following code uses the type and size optimization for BJData:
```cpp
// convert to UBJSON using the size and type optimization
// convert to BJData using the size and type optimization
auto v = json::to_bjdata(j, true, true);
```
@@ -178,7 +178,7 @@ as an array of uint8 values. The library implements this translation.
0xCA 0xFE 0xBA 0xBE // content
```
Note that subtype (42) is **not** serialized and that UBJSON has **no binary type**, and deserializing `v` would
Note that subtype (42) is **not** serialized and that BJData has **no binary type**, and deserializing `v` would
yield the following value:
```json
@@ -205,7 +205,7 @@ unsigned 8-bit integer. If no subtype is given, the generic binary subtype 0x00
auto v = json::to_bson(j);
```
`v` is a `std::vector<std::uint8t>` with the following 22 elements:
`v` is a `std::vector<std::uint8_t>` with the following 22 elements:
```c
0x16 0x00 0x00 0x00 // number of bytes in the document
@@ -247,7 +247,7 @@ byte array.
auto v = json::to_cbor(j);
```
`v` is a `std::vector<std::uint8t>` with the following 15 elements:
`v` is a `std::vector<std::uint8_t>` with the following 15 elements:
```c
0xA1 // map(1)
@@ -291,7 +291,7 @@ If no subtype is given, the bin family (bin8, bin16, bin32) is used.
auto v = json::to_msgpack(j);
```
`v` is a `std::vector<std::uint8t>` with the following 14 elements:
`v` is a `std::vector<std::uint8_t>` with the following 14 elements:
```c
0x81 // fixmap1
@@ -331,7 +331,7 @@ as an array of uint8 values. The library implements this translation.
auto v = json::to_ubjson(j);
```
`v` is a `std::vector<std::uint8t>` with the following 20 elements:
`v` is a `std::vector<std::uint8_t>` with the following 20 elements:
```c
0x7B // '{'
+91
View File
@@ -0,0 +1,91 @@
# Converting values
A `basic_json` value stores JSON data, but most of the time you want to move that data into ordinary C++ types (an
`#!cpp int`, a `#!cpp std::string`, a `#!cpp std::vector`, or one of your own structs) and back. This page describes how
these conversions work.
## Getting values out
The [`get`](../api/basic_json/get.md) function template returns a copy of the stored value converted to the requested
type:
```cpp
json j = R"({"name": "Mary", "age": 42, "hobbies": ["hiking", "reading"]})"_json;
auto name = j["name"].get<std::string>(); // "Mary"
auto age = j["age"].get<int>(); // 42
auto hobbies = j["hobbies"].get<std::vector<std::string>>(); // {"hiking", "reading"}
```
!!! note "Getting a string without quotes"
A frequent point of confusion: use [`get`](../api/basic_json/get.md), **not** [`dump`](serialization.md), to read a
string value. `#!cpp j["name"].get<std::string>()` yields `#!cpp Mary`, whereas `#!cpp j["name"].dump()` yields the
JSON text `#!cpp "Mary"` (**with** quotes), because `dump` always produces a JSON text.
Alternatively, [`get_to`](../api/basic_json/get_to.md) writes into an existing variable and deduces the target type,
which avoids repeating it:
??? example
```cpp
--8<-- "examples/get_to.cpp"
```
Output:
```json
--8<-- "examples/get_to.output"
```
The library already knows how to convert to and from the scalar types and the STL containers (such as
`#!cpp std::vector`, `#!cpp std::map`, `#!cpp std::array`, `#!cpp std::optional`, and many more). Converting a JSON
object back to a `#!cpp std::map` or a JSON array back to a `#!cpp std::vector` therefore works without any extra code:
```cpp
json j = {{"one", 1}, {"two", 2}};
auto m = j.get<std::map<std::string, int>>(); // {{"one", 1}, {"two", 2}}
```
## Implicit conversions
By default, a JSON value implicitly converts to a compatible C++ type, so the explicit `get` call can often be omitted:
```cpp
json j = "Hello";
std::string s = j; // implicit conversion, same as j.get<std::string>()
```
Implicit conversions are convenient but can be surprising (for example, in overload resolution or with `auto`). They can
be disabled by defining [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) to `#!cpp 0`,
which forces the explicit `get` form and can catch unintended conversions at compile time.
!!! warning "Conversions do not range-check numbers"
Just like C++ itself, the `get` family performs numeric conversions without range checks — retrieving a
floating-point value as an integer truncates it, and narrowing conversions may overflow. See
[number conversion](types/number_handling.md#number-conversion) for details and how to guard against it.
## Putting values in
The reverse direction works the same way: assigning or constructing a `json` from a C++ value converts it to JSON.
```cpp
std::vector<int> numbers = {1, 2, 3};
json j = numbers; // [1,2,3]
```
## Your own types
The conversions above are built in for standard types. To make the same syntax work for **your own** types, provide
`to_json`/`from_json` functions (or use one of the convenience macros). This is described in detail on the
[arbitrary types conversions](arbitrary_types.md) page. Enums can be mapped to strings as described in
[specializing enum conversion](enum_conversion.md).
## See also
- [`get`](../api/basic_json/get.md) - get a copy converted to a given type
- [`get_to`](../api/basic_json/get_to.md) - convert into an existing variable
- [`get_ref`](../api/basic_json/get_ref.md) / [`get_ptr`](../api/basic_json/get_ptr.md) - access the stored value without copying
- [Arbitrary types conversions](arbitrary_types.md) - support your own types
- [`JSON_USE_IMPLICIT_CONVERSIONS`](../api/macros/json_use_implicit_conversions.md) - toggle implicit conversions
@@ -0,0 +1,103 @@
# Creating JSON values
There are several ways to create a JSON value in memory. This page gives an overview; to read a value from JSON text
instead, see [parsing](parsing/index.md).
## From C++ values
Any value of a supported C++ type can be assigned to or used to construct a `json`:
```cpp
json j_number = 42;
json j_float = 3.141;
json j_string = "Hello";
json j_boolean = true;
json j_null = nullptr;
json j_vector = std::vector<int>{1, 2, 3}; // array
```
See [converting values](conversions.md) for the full set of supported types.
## With initializer lists
Objects and arrays can be written concisely with brace-enclosed initializer lists:
```cpp
// an array
json array = {1, 2, 3, 4};
// an object (a list of key/value pairs)
json object = {
{"pi", 3.141},
{"happy", true},
{"name", "Niels"},
{"nothing", nullptr},
{"list", {1, 0, 2}},
{"object", {{"currency", "USD"}, {"value", 42.99}}}
};
```
The library decides between an array and an object based on the content: a list whose elements are all two-element lists
with a string as the first element is treated as an object, everything else as an array.
!!! warning "Ambiguous cases: `#!cpp {}` vs. `#!cpp []`"
Because the same `#!cpp {}` syntax is used for both arrays and objects, some cases are ambiguous. To force a
particular type, use the explicit factory functions [`json::array`](../api/basic_json/array.md) and
[`json::object`](../api/basic_json/object.md):
```cpp
json empty_array_explicit = json::array(); // []
json empty_object_explicit = json::object(); // {}
// a JSON array with one object, not an object with one member
json array_of_objects = json::array({{"key", "value"}}); // [{"key":"value"}]
```
Related to this, single-element brace initialization such as `#!cpp json j{value};` wraps the element in a
single-element **array** by default, and its behavior even differs between compilers. See the
[FAQ](../home/faq.md#brace-initialization-yields-arrays) for details and the opt-in
[`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) macro.
## Building incrementally
A value can also be built up piece by piece. Accessing a non-existing object key or array index with
[`operator[]`](element_access/unchecked_access.md) creates the element on the fly:
```cpp
json j; // null
j["answer"]["everything"] = 42; // becomes an object
j["list"] = {1, 0, 2};
j["list"].push_back(3); // [1,0,2,3]
```
See [modifying values](modifying_values.md) for [`push_back`](../api/basic_json/push_back.md),
[`emplace`](../api/basic_json/emplace.md), and related functions.
## With the `_json` literal
The `_json` [user-defined literal](../api/operator_literal_json.md) parses a string at the call site and is a
convenient way to write a JSON value inline:
??? example
```cpp
--8<-- "examples/operator_literal_json.cpp"
```
Output:
```json
--8<-- "examples/operator_literal_json.output"
```
Note this **parses** the string, so `#!cpp "42"_json` is the number `#!cpp 42`, whereas `#!cpp json("42")` is the JSON
string `#!json "42"`.
## See also
- [`basic_json` constructors](../api/basic_json/basic_json.md) - all ways to construct a value
- [`array`](../api/basic_json/array.md) / [`object`](../api/basic_json/object.md) - force array or object type
- [`operator""_json`](../api/operator_literal_json.md) - the `_json` literal
- [Converting values](conversions.md) - which C++ types can be used
- [Parsing](parsing/index.md) - create a value from JSON text
@@ -29,7 +29,7 @@ otherwise.
| `#!cpp j.at("hobbies").at(0)` | `#!json "hiking"` |
| `#!cpp j.at("hobbies").at(1)` | `#!json "reading"` |
The return value is a reference, so it can be modified by the original value.
The return value is a reference, so it can be used to modify the original value.
??? example "Write access"
@@ -62,7 +62,7 @@ non-existing, an exception is thrown.
[json.exception.out_of_range.401] array index 3 is out of range
```
When you [extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) are enabled by
When [extended diagnostic messages](../../home/exceptions.md#extended-diagnostic-messages) are enabled by
defining [`JSON_DIAGNOSTICS`](../../api/macros/json_diagnostics.md), the exception further gives information where
the key or index is missing or out of range.
@@ -41,7 +41,7 @@ you want to access and a default value in case there is no value stored with tha
The value function is a template, and the return type of the function is determined by the type of the provided
default value unless otherwise specified. This can have unexpected effects. In the example below, we store a 64-bit
unsigned integer. We get exactly that value when using [`operator[]`](../../api/basic_json/operator[].md). However,
when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. The occurs, because `#!c 0`
when we call `value` and provide `#!c 0` as default value, then `#!c -1` is returned. This occurs, because `#!c 0`
has type `#!c int` which overflows when handling the value `#!c 18446744073709551615`.
To address this issue, either provide a correctly typed default value or use the template parameter to specify the
+53
View File
@@ -0,0 +1,53 @@
# Features
This section describes the features of the library in detail. If you are new to the library, the pages below are
roughly ordered along a typical workflow: create or parse a value, access and modify it, convert it to and from your own
C++ types, and finally serialize it again.
## Creating and reading values
- [Creating JSON values](creating_values.md) — build values from literals, initializer lists, and STL containers, and
understand the `#!cpp {}` vs. `#!cpp []` ambiguity.
- [Parsing](parsing/index.md) — read a JSON value from a string, file, or stream, including
[JSON Lines](parsing/json_lines.md), [callbacks](parsing/parser_callbacks.md), the
[SAX interface](parsing/sax_interface.md), and [error handling](parsing/parse_exceptions.md).
- [Comments](comments.md) and [trailing commas](trailing_commas.md) — opt-in relaxations of the JSON grammar.
## Accessing and modifying values
- [Element access](element_access/index.md) — unchecked ([`operator[]`](element_access/unchecked_access.md)),
checked ([`at`](element_access/checked_access.md)), and access with a
[default value](element_access/default_value.md).
- [JSON Pointer](json_pointer.md) — address values deep inside a document with [RFC 6901](https://tools.ietf.org/html/rfc6901) pointers.
- [Iterators](iterators.md) — traverse arrays and objects.
- [Modifying values](modifying_values.md) — add, update, merge, and remove elements.
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) — apply and compute structured changes.
## Converting to and from C++ types
- [Converting values](conversions.md) — get values out with [`get`](../api/basic_json/get.md)/[`get_to`](../api/basic_json/get_to.md),
and understand implicit conversions.
- [Arbitrary types conversions](arbitrary_types.md) — teach the library about your own structs and classes.
- [Specializing enum conversion](enum_conversion.md) — map enums to strings instead of integers.
## Serializing values
- [Serialization](serialization.md) — turn a value back into JSON text with [`dump`](../api/basic_json/dump.md),
including pretty-printing and handling of non-ASCII and invalid UTF-8.
- [Binary formats](binary_formats/index.md) — encode values more compactly as
[BJData](binary_formats/bjdata.md), [BSON](binary_formats/bson.md), [CBOR](binary_formats/cbor.md),
[MessagePack](binary_formats/messagepack.md), or [UBJSON](binary_formats/ubjson.md).
- [Binary values](binary_values.md) — store and exchange raw byte sequences.
## How values are stored and configured
- [Types](types/index.md) and [number handling](types/number_handling.md) — how JSON types map to C++ types and how
numbers are treated.
- [Object order](object_order.md) — keep insertion order with [`ordered_json`](../api/ordered_json.md).
- [Runtime assertions](assertions.md), [supported macros](macros.md), the [`nlohmann` namespace](namespace.md), and
[C++ modules](modules.md) — build-time and runtime configuration.
!!! tip "Looking for a specific function?"
This section gives conceptual overviews. For the precise signature, parameters, and return value of a function, see
the [API Documentation](../api/basic_json/index.md).
+1 -1
View File
@@ -130,7 +130,7 @@ for (auto& [key, val] : j_object.items())
### Iterating strings and binary values
Note that "value" means a JSON value in this setting, not values stored in the underlying containers. That is, `*begin()` returns the complete string or binary array and is also safe the underlying string or binary array is empty.
Note that "value" means a JSON value in this setting, not values stored in the underlying containers. That is, `*begin()` returns the complete string or binary array and is also safe if the underlying string or binary array is empty.
??? example
+1 -1
View File
@@ -28,7 +28,7 @@ The library can also calculate a JSON patch (i.e., a **diff**) given two JSON va
For two JSON values *source* and *target*, the following code yields always true:
```cüü
```cpp
source.patch(diff(source, target)) == target;
```
@@ -0,0 +1,77 @@
# Modifying values
Once a JSON value exists, its content can be changed: elements can be added, replaced, merged, and removed. This page
gives an overview of the available operations. For read access, see [element access](element_access/index.md).
## Adding to arrays
New elements are appended to an array with [`push_back`](../api/basic_json/push_back.md) or constructed in place with
[`emplace_back`](../api/basic_json/emplace_back.md). If the value is `#!json null`, it is converted to an array first, so
these functions can also be used to build an array from scratch.
```cpp
json j; // null
j.push_back(1); // [1]
j.push_back(2); // [1,2]
j.emplace_back(3); // [1,2,3]
// operator+= is a shorthand for push_back
j += 4; // [1,2,3,4]
```
## Adding to objects
The most common way to add or replace a member is [`operator[]`](element_access/unchecked_access.md), which inserts the
key if it does not exist yet:
```cpp
json j;
j["name"] = "Mary"; // {"name":"Mary"}
j["name"] = "John"; // {"name":"John"} (replaced)
```
[`emplace`](../api/basic_json/emplace.md) inserts a member only if the key is not already present, and reports whether
the insertion happened — useful for "add if absent" semantics.
## Merging objects
To merge one object into another, [`update`](../api/basic_json/update.md) copies all members from another object,
overwriting existing keys (similar to Python's `dict.update`). This is the idiomatic way to combine two objects.
??? example
```cpp
--8<-- "examples/update.cpp"
```
Output:
```json
--8<-- "examples/update.output"
```
For a recursive merge that follows [RFC 7386](https://tools.ietf.org/html/rfc7386), see
[JSON Merge Patch](merge_patch.md). To apply a sequence of well-defined edit operations, see
[JSON Patch](json_patch.md).
## Removing elements
Elements are removed with [`erase`](../api/basic_json/erase.md), which accepts an object key, an array index, or an
iterator. [`clear`](../api/basic_json/clear.md) empties a value while keeping its type, and
[`operator[]`](element_access/unchecked_access.md) combined with assignment can overwrite a value entirely.
```cpp
json j = {{"a", 1}, {"b", 2}, {"c", 3}};
j.erase("b"); // {"a":1,"c":3}
json a = {1, 2, 3, 4};
a.erase(1); // [1,3,4] (erase by index)
```
## See also
- [`push_back`](../api/basic_json/push_back.md) / [`emplace_back`](../api/basic_json/emplace_back.md) - append to an array
- [`emplace`](../api/basic_json/emplace.md) - insert into an object if the key is absent
- [`update`](../api/basic_json/update.md) - merge objects
- [`erase`](../api/basic_json/erase.md) / [`clear`](../api/basic_json/clear.md) - remove elements
- [JSON Patch and Diff](json_patch.md) and [JSON Merge Patch](merge_patch.md) - structured modifications
+2 -1
View File
@@ -59,7 +59,7 @@ may complain about undefined references.
Different versions are not necessarily ABI-incompatible, but the project does not actively track changes in the ABI and
recommends that all parts of a codebase exchanging library types be built with the same version. Users can, **at their
own risk**, disable the version component of the linline namespace, allowing different versions but not
own risk**, disable the version component of the inline namespace, allowing different versions but not
configurations to be used in cases where the linker would otherwise output undefined reference errors.
To do so, define [`NLOHMANN_JSON_NAMESPACE_NO_VERSION`](../api/macros/nlohmann_json_namespace_no_version.md) to `1`.
@@ -71,6 +71,7 @@ section to emulate the effect of the `NLOHMANN_JSON_NAMESPACE_NO_VERSION` macro.
Disabling the namespace version component and mixing ABI-incompatible versions will result in crashes or incorrect
behavior. You have been warned!
## Disabling the inline namespace completely
When interoperability with code using a pre-3.11.0 version of the library is required, users can, **at their own risk**
+3 -3
View File
@@ -37,7 +37,7 @@ The default type `nlohmann::json` uses a `std::map` to store JSON objects, and t
## Alternative behavior: preserve insertion order
If you do want to preserve the **insertion order**, you can try the type [`nlohmann::ordered_json`](https://github.com/nlohmann/json/issues/2179).
If you do want to preserve the **insertion order**, you can use the type [`nlohmann::ordered_json`](../api/ordered_json.md).
??? example
@@ -103,7 +103,7 @@ Assume file `input.json` contains the JSON object above:
```json
{
"one": 1,
"three": 3
"two": 2,
"three": 3,
"two": 2
}
```
+52 -4
View File
@@ -1,13 +1,61 @@
# Parsing
!!! note
This page is under construction.
This library can create a JSON value from a wide range of inputs. This page gives an overview of the available parsing
functions and how they behave; the linked pages go into more detail.
## Input
The [`parse`](../../api/basic_json/parse.md) function reads a JSON value from an input. The input can be
- a string (`#!cpp std::string`, C string, or string literal),
- a `#!cpp std::istream` (e.g., an `#!cpp std::ifstream` reading from a file),
- a `#!cpp FILE*` pointer,
- a pair of iterators over a contiguous range (e.g., a `#!cpp std::vector<std::uint8_t>`), or
- a contiguous container.
```cpp
// parse from a string
json j = json::parse(R"({"happy": true, "pi": 3.141})");
// parse from a file
std::ifstream f("example.json");
json data = json::parse(f);
```
The input must be encoded in UTF-8; other encodings are not supported. A single input may contain only one JSON value.
Inputs consisting of multiple values separated by newlines are handled by the [JSON Lines](json_lines.md) format.
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).
## SAX vs. DOM parsing
The library offers two parsing models:
- **DOM parsing** (the default): the complete input is read and stored as an in-memory `basic_json` value that can be
traversed and modified freely. This is what [`parse`](../../api/basic_json/parse.md) does, and it is the right choice
for most use cases.
- **SAX parsing**: instead of building a value, the parser reports events (such as "a string was read" or "an object
started") to a handler that you implement. This avoids building the full value in memory and is useful for very large
inputs or when you only need to extract parts of the input. See the [SAX interface](sax_interface.md) for details and
[`sax_parse`](../../api/basic_json/sax_parse.md) for the API.
You can influence a DOM parse without switching to the SAX interface by passing a
[parser callback](parser_callbacks.md), which is called during parsing and can, for example, discard parts of the input.
## Exceptions
See [parsing and exceptions](parse_exceptions.md).
When the input is not valid JSON, the `parse` function throws an exception by default. If exceptions are undesired or
unavailable, the parser can instead return a discarded value, or [`accept`](../../api/basic_json/accept.md) can be used
to only check whether an input is valid JSON. See [parsing and exceptions](parse_exceptions.md) for the available
options.
## See also
- [`parse`](../../api/basic_json/parse.md) - deserialize from a compatible input
- [`accept`](../../api/basic_json/accept.md) - check if the input is valid JSON
- [`sax_parse`](../../api/basic_json/sax_parse.md) - generate SAX events
- [JSON Lines](json_lines.md) - parse newline-delimited JSON
- [parser callbacks](parser_callbacks.md) - influence the parsing by a callback function
- [SAX interface](sax_interface.md) - implement a custom SAX handler
- [parsing and exceptions](parse_exceptions.md) - control error handling
+129
View File
@@ -0,0 +1,129 @@
# Serialization
Serialization is the process of turning a JSON value back into JSON text. It is the counterpart to
[parsing](parsing/index.md). The central function is [`dump`](../api/basic_json/dump.md), which returns the JSON text as
a string.
```cpp
json j = {{"pi", 3.141}, {"happy", true}};
std::string s = j.dump(); // {"happy":true,"pi":3.141}
```
To write a value directly to a stream (for example, a file or `#!cpp std::cout`), the
[`operator<<`](../api/operator_ltlt.md) is provided:
```cpp
std::cout << j << std::endl;
```
!!! note "String, not raw value"
`dump` always returns a **JSON text**. Serializing a JSON string therefore includes the surrounding quotes and
escapes special characters. To obtain the *contained* string value without quotes, use
[`get<std::string>()`](conversions.md) instead of `dump`. See the [converting values](conversions.md) page.
## Pretty-printing
By default, `dump` produces the most compact representation without any superfluous whitespace. Passing a non-negative
`indent` argument pretty-prints the output with the given number of spaces per level:
??? example
```cpp
--8<-- "examples/dump.cpp"
```
Output:
```json
--8<-- "examples/dump.output"
```
The indentation character can be changed with the second argument (e.g., a tab `#!cpp '\t'`). An `indent` of `0` inserts
newlines but no leading spaces, and the default of `#!cpp -1` selects the compact single-line form.
## Non-ASCII characters
Strings are stored and serialized as UTF-8 (see [types](types/index.md#strings)). By default, `dump` copies valid
non-ASCII characters as-is. Setting the third argument `ensure_ascii` to `#!cpp true` escapes all non-ASCII characters
with `\uXXXX` sequences, so that the output contains only ASCII characters:
```cpp
json j = "苹果";
j.dump(); // "苹果"
j.dump(-1, ' ', true); // "苹果"
```
## Handling invalid UTF-8
If a string contains invalid UTF-8 sequences (for example, because it holds data in another encoding such as Latin-1),
serialization fails by default. The fourth argument of `dump` selects an
[`error_handler`](../api/basic_json/error_handler_t.md):
- `strict` (default) — throw a [`type_error.316`](../home/exceptions.md#jsonexceptiontype_error316) exception.
- `replace` — replace invalid bytes with the Unicode replacement character U+FFFD (``).
- `ignore` — silently drop invalid bytes.
??? example
```cpp
--8<-- "examples/error_handler_t.cpp"
```
Output:
```json
--8<-- "examples/error_handler_t.output"
```
!!! tip "Avoiding invalid UTF-8"
The best fix is to ensure that all strings are UTF-8 encoded before storing them. See the
[FAQ on non-ASCII characters](../home/faq.md#parse-errors-reading-non-ascii-characters) for how to convert wide or
Latin-1 strings.
## Numbers, NaN, and binary values
- **Numbers** are serialized with enough precision to round-trip; see [number serialization](types/number_handling.md#number-serialization).
- **NaN and infinity** cannot be represented in JSON and are serialized as `#!json null`; see
[NaN handling](types/number_handling.md#nan-handling). The [binary formats](binary_formats/index.md) can preserve
them.
- **Binary values** have no JSON representation and are serialized as a helper object for debugging only; see
[binary values](binary_values.md#serialization).
## Using `std::format`, `std::print`, and `fmt`
Since version 3.12.0, JSON values can be formatted directly with C++20's
[`std::format`](https://en.cppreference.com/w/cpp/utility/format/format) whenever the standard library provides the
`<format>` header (controlled by [`JSON_HAS_STD_FORMAT`](../api/macros/json_has_std_format.md)). This is enabled by the
[`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) specialization, which also makes JSON values work with
`std::format_to` and with C++23's `std::print`/`std::println`:
```cpp
std::print("{}", j); // compact, like j.dump()
std::print("{:2}", j); // pretty-printed with indent 2 (like j.dump(2))
std::println("{:#}", j); // pretty-printed with the default indent
```
The format spec mirrors the `dump` parameters: `#!cpp "{:#}"` pretty-prints, a width such as `#!cpp "{:2}"` sets the
indent, and a fill-and-align prefix such as `#!cpp "{:.>#}"` sets the indent character.
For the [{fmt}](https://github.com/fmtlib/fmt) library, the library ships a
[`format_as`](../api/basic_json/format_as.md) helper. Note its behavior depends on the `fmt` version; see the
[FAQ entry](../home/faq.md#using-json-values-with-stdformat-or-fmt) for the details and a recipe for a full
`fmt::formatter` specialization.
## Serializing to other formats
Besides JSON text, a value can also be serialized to the more compact [binary formats](binary_formats/index.md)
(BJData, BSON, CBOR, MessagePack, UBJSON).
## See also
- [`dump`](../api/basic_json/dump.md) - serialize to a JSON-formatted string
- [`operator<<`](../api/operator_ltlt.md) - serialize to a stream
- [`to_string`](../api/basic_json/to_string.md) - user-defined-conversion helper
- [`std::formatter<basic_json>`](../api/basic_json/std_formatter.md) - use JSON values with `std::format` and `std::print`
- [`format_as`](../api/basic_json/format_as.md) - use JSON values with the {fmt} library
- [Parsing](parsing/index.md) - the reverse operation
+1 -1
View File
@@ -137,7 +137,7 @@ The choice of `object_t` influences the behavior of the JSON class. With the def
### Key order
The order name/value pairs are added to the object are *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON objects.
The order in which name/value pairs are added to the object is *not* preserved by the library. Therefore, iterating an object may return name/value pairs in a different order than they were originally stored. In fact, keys will be traversed in alphabetical order as `std::map` with `std::less` is used by default. Please note this behavior conforms to [RFC 8259](https://tools.ietf.org/html/rfc8259), because any order implements the specified "unordered" nature of JSON objects.
### Limits
@@ -91,7 +91,7 @@ This is the same behavior as the code `#!c double x = 3.141592653589793238462643
!!! success "Interoperability"
- The library interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
- The library is interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
larger than the described range $[-2^{53}+1, 2^{53}-1]$.
- All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`.
This also concurs with the specification above.
@@ -153,7 +153,7 @@ NaN (not-a-number) cannot be expressed with the number syntax described above an
That is, there is no way to *parse* a NaN value. However, assignments can store NaN values in a JSON value.
This library serializes NaN values as `#!js null`. This corresponds to the behavior of JavaScript's
This library serializes NaN values as `#!js null`. This corresponds to the behavior of JavaScript's
[`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp) function.
!!! example
@@ -182,7 +182,7 @@ This library serializes NaN values as `#!js null`. This corresponds to the beha
### Number comparison
Floating-point inside JSON values numbers are compared with `#!c json::number_float_t::operator==` which is
Floating-point numbers inside JSON values are compared with `#!c json::number_float_t::operator==` which is
`#!c double::operator==` by default.
!!! example "Alternative comparison functions"
@@ -203,8 +203,8 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
```cpp
bool my_equal(const_reference lhs, const_reference rhs)
{
const auto lhs_type lhs.type();
const auto rhs_type rhs.type();
const auto lhs_type = lhs.type();
const auto rhs_type = rhs.type();
if (lhs_type == rhs_type)
{
switch(lhs_type)
@@ -230,7 +230,7 @@ Floating-point inside JSON values numbers are compared with `#!c json::number_fl
### Number conversion
Just like the C++ language itself, the `get` family of functions allows conversions between unsigned and signed
integers, and between integers and floating-point values to integers. This behavior may be surprising.
integers, and between integers and floating-point values. This behavior may be surprising.
!!! warning "Unconditional number conversions"
+8 -3
View File
@@ -57,6 +57,7 @@ nav:
- home/customers.md
- home/sponsors.md
- Features:
- features/index.md
- features/arbitrary_types.md
- Binary Formats:
- features/binary_formats/index.md
@@ -67,16 +68,18 @@ nav:
- features/binary_formats/ubjson.md
- features/binary_values.md
- features/comments.md
- features/trailing_commas.md
- features/conversions.md
- features/creating_values.md
- Element Access:
- features/element_access/index.md
- features/element_access/unchecked_access.md
- features/element_access/checked_access.md
- features/element_access/default_value.md
- features/iterators.md
- features/json_pointer.md
- features/json_patch.md
- features/merge_patch.md
- features/json_patch.md
- features/json_pointer.md
- features/modifying_values.md
- features/modules.md
- 'nlohmann Namespace': features/namespace.md
- features/object_order.md
@@ -87,8 +90,10 @@ nav:
- features/parsing/parser_callbacks.md
- features/parsing/sax_interface.md
- features/assertions.md
- features/serialization.md
- features/enum_conversion.md
- features/macros.md
- features/trailing_commas.md
- Types:
- features/types/index.md
- features/types/number_handling.md
+58 -21
View File
@@ -12,7 +12,7 @@
#include <array> // array
#include <cmath> // ldexp
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
#include <cstdio> // snprintf
#include <cstring> // memcpy
#include <iterator> // back_inserter
@@ -2908,18 +2908,7 @@ class binary_reader
const NumberType len,
string_t& result)
{
bool success = true;
for (NumberType i = 0; i < len; i++)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
{
success = false;
break;
}
result.push_back(static_cast<typename string_t::value_type>(current));
}
return success;
return get_bytes(format, len, "string", result);
}
/*!
@@ -2941,18 +2930,66 @@ class binary_reader
const NumberType len,
binary_t& result)
{
bool success = true;
for (NumberType i = 0; i < len; i++)
return get_bytes(format, len, "binary", result);
}
/*!
@brief read @a len bytes from the input into a string or byte container
@tparam NumberType the type of the length
@tparam ContainerType the destination container (string_t or binary_t)
@param[in] format the current format (for diagnostics)
@param[in] len number of bytes to read
@param[in] context further context information (for diagnostics)
@param[out] result container the bytes are appended to
@return whether reading completed
@note We cannot reserve @a len bytes for the result up front, because
@a len may be far larger than the actual input. Instead we read in
bounded chunks, so the peak allocation is capped regardless of the
claimed length while the per-byte loop is replaced by block copies
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
detects a premature end of input.
*/
template<typename NumberType, typename ContainerType>
bool get_bytes(const input_format_t format,
NumberType len,
const char* context,
ContainerType& result)
{
// upper bound on the number of bytes read (and allocated) per chunk
constexpr std::size_t chunk_size = 4096;
while (len > 0)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
// number of bytes to read this iteration: min(chunk_size, len),
// computed without truncating chunk_size to a narrow NumberType
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
? static_cast<std::size_t>(len)
: chunk_size;
const std::size_t old_size = result.size();
result.resize(old_size + wanted);
// resize() is required to make size() exactly old_size + wanted;
// that is the room get_elements() is allowed to write into
JSON_ASSERT(result.size() == old_size + wanted);
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
chars_read += bytes_read;
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
{
success = false;
break;
// premature end of input: shrink to what was actually read and
// report the failure at the first missing byte (same position
// accounting as get_to() for partial number reads)
result.resize(old_size + bytes_read);
++chars_read;
current = char_traits<char_type>::eof();
return unexpect_eof(format, context);
}
result.push_back(static_cast<typename binary_t::value_type>(current));
// a full chunk was read; get_elements() never returns more than requested
JSON_ASSERT(bytes_read == wanted);
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
}
return success;
return true;
}
/*!
@@ -161,8 +161,18 @@ class iterator_input_adapter
public:
using char_type = typename std::iterator_traits<IteratorType>::value_type;
// Whether the lexer may reconstruct already-consumed input on demand (for
// diagnostics) instead of copying every scanned character eagerly. This is
// only sound for multi-pass, randomly-addressable byte input: the iterator
// must be random-access (so the consumed prefix can be revisited in O(1))
// and each element must map 1:1 to an input byte (wide inputs are wrapped
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
&& sizeof(char_type) == 1;
iterator_input_adapter(IteratorType first, IteratorType last)
: current(std::move(first)), end(std::move(last))
: begin(first), current(std::move(first)), end(std::move(last))
{}
typename char_traits<char_type>::int_type get_character()
@@ -177,9 +187,67 @@ class iterator_input_adapter
return char_traits<char_type>::eof();
}
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
// number of characters consumed from the input so far
std::size_t get_consumed_count() const
{
return static_cast<std::size_t>(std::distance(begin, current));
}
// append the already-consumed characters in the half-open range
// [first_index, last_index) to @a out; only valid when supports_seek
template<typename ContainerType>
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
{
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
out.insert(out.end(), from, to);
}
// Copy up to count * sizeof(T) bytes into dest, returning the number of
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
// single std::memcpy; for general iterators we fall back to processing the
// range one-by-one.
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
}
private:
// whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string)
static constexpr bool iterator_is_contiguous =
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> ||
#endif
std::is_pointer<IteratorType>::value;
// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{
const std::size_t wanted = count * sizeof(T);
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0))
{
// the copy must stay within both buffers: the caller-provided
// destination holds `wanted` bytes and the remaining input range
// holds `available` bytes, and `copied` is the minimum of the two
JSON_ASSERT(copied <= wanted); // does not overrun the destination
JSON_ASSERT(copied <= available); // does not read past the input end
// &*current yields the raw address for both raw pointers and
// non-pointer contiguous iterators (e.g. std::vector's iterator)
std::memcpy(dest, &*current, copied);
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
}
return copied;
}
// general fallback: copy the range one element at a time
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
{
auto* ptr = reinterpret_cast<char*>(dest);
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
@@ -197,7 +265,7 @@ class iterator_input_adapter
return count * sizeof(T);
}
private:
IteratorType begin;
IteratorType current;
IteratorType end;
+100 -7
View File
@@ -103,6 +103,28 @@ class lexer_base
}
}
};
// Detect whether an input adapter can reconstruct already-consumed input on
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
// are treated as non-seekable streaming input, for which the lexer keeps
// copying every scanned character eagerly. The value is read via tag dispatch
// on is_detected so the flag is only referenced for adapters that provide it.
template<typename InputAdapterType>
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
{
return InputAdapterType::supports_seek;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
{
return false;
}
/*!
@brief lexical analysis
@@ -118,6 +140,12 @@ class lexer : public lexer_base<BasicJsonType>
using char_type = typename InputAdapterType::char_type;
using char_int_type = typename char_traits<char_type>::int_type;
/// whether the last read token can be reconstructed from the input adapter
/// on demand (in error paths) instead of being copied on every scanned
/// character; see input_adapter_supports_seek
static constexpr bool lazy_token_string =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
public:
using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -1327,8 +1355,24 @@ scan_number_done:
void reset() noexcept
{
token_buffer.clear();
token_string.clear();
decimal_point_position = std::string::npos;
note_token_start(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: remember where the current token starts so it can be
/// reconstructed from the input on error; current has already been
/// consumed, hence the -1
void note_token_start(std::true_type /*lazy*/) noexcept
{
token_string_start = ia.get_consumed_count() - 1;
}
/// streaming adapter: start copying the token eagerly, beginning with the
/// already-read first character
void note_token_start(std::false_type /*lazy*/) noexcept
{
token_string.clear();
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
@@ -1357,10 +1401,9 @@ scan_number_done:
current = ia.get_character();
}
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
// seekable adapters reconstruct the token lazily on error (see
// get_token_string), so the eager per-character copy is skipped
capture_char(std::integral_constant<bool, lazy_token_string> {});
if (current == '\n')
{
@@ -1371,6 +1414,18 @@ scan_number_done:
return current;
}
/// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: copy the scanned character into token_string
void capture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
}
/*!
@brief unget current character (read it again on next get)
@@ -1398,6 +1453,15 @@ scan_number_done:
--position.chars_read_current_line;
}
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: nothing was captured, so nothing to undo
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: drop the character copied by the matching get()
void uncapture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
JSON_ASSERT(!token_string.empty());
@@ -1455,14 +1519,38 @@ scan_number_done:
return position;
}
/// 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
{
// a pending unget of a real (non-EOF) character means that character
// was consumed from the input but is not part of the token; EOF is
// never consumed, so it must not be subtracted (mirrors unget())
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{
ia.copy_consumed_range(token_string_start, stop, out);
}
return out;
}
/// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{
return token_string;
}
/// return the last read token (for errors only). Will never contain EOF
/// (an arbitrary value that is not a valid char value, often -1), because
/// 255 may legitimately occur. May contain NUL, which should be escaped.
std::string get_token_string() const
{
std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// escape control characters
std::string result;
for (const auto c : token_string)
for (const auto c : chars)
{
if (static_cast<unsigned char>(c) <= '\x1F')
{
@@ -1623,9 +1711,14 @@ scan_number_done:
/// the start position of the current token
position_t position {};
/// raw input token string (for error messages)
/// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars)
std::size_t token_string_start = 0;
/// buffer for variable-length tokens (numbers, strings)
string_t token_buffer {};
@@ -13,8 +13,6 @@
#include <nlohmann/detail/abi_macros.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
/*!
@brief Default base class of the @ref basic_json class.
@@ -25,13 +23,27 @@ of @ref basic_json do not require complex case distinctions
@ref basic_json always has a base class.
By default, this class is used because it is empty and thus has no effect
on the behavior of @ref basic_json.
@note This class intentionally lives in namespace @ref nlohmann rather than
@ref nlohmann::detail. Every @ref basic_json specialization derives from
it (via @ref detail::json_base_class) unless a custom base class is
supplied, which makes its namespace an associated namespace of
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
it lived in `nlohmann::detail`, that namespace - and with it the
library's internal `to_json`/`from_json` overloads - would leak into
ADL for any unqualified `to_json`/`from_json` call a user makes
involving a @ref basic_json argument, silently shadowing the user's own
overloads in some cases.
*/
struct json_default_base {};
namespace detail
{
template<class T>
using json_base_class = typename std::conditional <
std::is_same<T, void>::value,
json_default_base,
::nlohmann::json_default_base,
T
>::type;
+1 -1
View File
@@ -5,7 +5,7 @@
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2018 The Abseil Authors
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT AND Apache-2.0
#pragma once
+245 -35
View File
@@ -3405,7 +3405,7 @@ NLOHMANN_JSON_NAMESPACE_END
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-FileCopyrightText: 2018 The Abseil Authors
// SPDX-License-Identifier: MIT
// SPDX-License-Identifier: MIT AND Apache-2.0
@@ -6811,7 +6811,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <array> // array
#include <cmath> // ldexp
#include <cstddef> // size_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t, uintmax_t
#include <cstdio> // snprintf
#include <cstring> // memcpy
#include <iterator> // back_inserter
@@ -6993,8 +6993,18 @@ class iterator_input_adapter
public:
using char_type = typename std::iterator_traits<IteratorType>::value_type;
// Whether the lexer may reconstruct already-consumed input on demand (for
// diagnostics) instead of copying every scanned character eagerly. This is
// only sound for multi-pass, randomly-addressable byte input: the iterator
// must be random-access (so the consumed prefix can be revisited in O(1))
// and each element must map 1:1 to an input byte (wide inputs are wrapped
// in wide_string_input_adapter, which does not expose this).
static constexpr bool supports_seek =
std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value
&& sizeof(char_type) == 1;
iterator_input_adapter(IteratorType first, IteratorType last)
: current(std::move(first)), end(std::move(last))
: begin(first), current(std::move(first)), end(std::move(last))
{}
typename char_traits<char_type>::int_type get_character()
@@ -7009,9 +7019,67 @@ class iterator_input_adapter
return char_traits<char_type>::eof();
}
// for general iterators, we cannot really do something better than falling back to processing the range one-by-one
// number of characters consumed from the input so far
std::size_t get_consumed_count() const
{
return static_cast<std::size_t>(std::distance(begin, current));
}
// append the already-consumed characters in the half-open range
// [first_index, last_index) to @a out; only valid when supports_seek
template<typename ContainerType>
void copy_consumed_range(std::size_t first_index, std::size_t last_index, ContainerType& out) const
{
const auto from = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(first_index));
const auto to = std::next(begin, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(last_index));
out.insert(out.end(), from, to);
}
// Copy up to count * sizeof(T) bytes into dest, returning the number of
// bytes actually read. For contiguous iterators (e.g. pointers) this is a
// single std::memcpy; for general iterators we fall back to processing the
// range one-by-one.
template<class T>
std::size_t get_elements(T* dest, std::size_t count = 1)
{
return get_elements_impl(dest, count, std::integral_constant<bool, iterator_is_contiguous> {});
}
private:
// whether IteratorType refers to a contiguous range and therefore supports
// a std::memcpy fast path (pointers always do; in C++20 we can also detect
// library iterators such as those of std::vector and std::string)
static constexpr bool iterator_is_contiguous =
#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20)
std::contiguous_iterator<IteratorType> ||
#endif
std::is_pointer<IteratorType>::value;
// contiguous fast path: bulk copy the remaining range with std::memcpy
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::true_type /*contiguous*/)
{
const std::size_t wanted = count * sizeof(T);
const std::size_t available = static_cast<std::size_t>(std::distance(current, end)) * sizeof(char_type);
const std::size_t copied = (std::min)(wanted, available);
if (JSON_HEDLEY_LIKELY(copied != 0))
{
// the copy must stay within both buffers: the caller-provided
// destination holds `wanted` bytes and the remaining input range
// holds `available` bytes, and `copied` is the minimum of the two
JSON_ASSERT(copied <= wanted); // does not overrun the destination
JSON_ASSERT(copied <= available); // does not read past the input end
// &*current yields the raw address for both raw pointers and
// non-pointer contiguous iterators (e.g. std::vector's iterator)
std::memcpy(dest, &*current, copied);
std::advance(current, static_cast<typename std::iterator_traits<IteratorType>::difference_type>(copied / sizeof(char_type)));
}
return copied;
}
// general fallback: copy the range one element at a time
template<class T>
std::size_t get_elements_impl(T* dest, std::size_t count, std::false_type /*contiguous*/)
{
auto* ptr = reinterpret_cast<char*>(dest);
for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)
@@ -7029,7 +7097,7 @@ class iterator_input_adapter
return count * sizeof(T);
}
private:
IteratorType begin;
IteratorType current;
IteratorType end;
@@ -7510,6 +7578,28 @@ class lexer_base
}
}
};
// Detect whether an input adapter can reconstruct already-consumed input on
// demand (see iterator_input_adapter::supports_seek). Adapters that do not
// expose the flag - e.g. file, stream, wide-string, and user-defined adapters -
// are treated as non-seekable streaming input, for which the lexer keeps
// copying every scanned character eagerly. The value is read via tag dispatch
// on is_detected so the flag is only referenced for adapters that provide it.
template<typename InputAdapterType>
using detect_supports_seek = decltype(InputAdapterType::supports_seek);
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::true_type /*detected*/)
{
return InputAdapterType::supports_seek;
}
template<typename InputAdapterType>
constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
{
return false;
}
/*!
@brief lexical analysis
@@ -7525,6 +7615,12 @@ class lexer : public lexer_base<BasicJsonType>
using char_type = typename InputAdapterType::char_type;
using char_int_type = typename char_traits<char_type>::int_type;
/// whether the last read token can be reconstructed from the input adapter
/// on demand (in error paths) instead of being copied on every scanned
/// character; see input_adapter_supports_seek
static constexpr bool lazy_token_string =
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
public:
using token_type = typename lexer_base<BasicJsonType>::token_type;
@@ -8734,8 +8830,24 @@ scan_number_done:
void reset() noexcept
{
token_buffer.clear();
token_string.clear();
decimal_point_position = std::string::npos;
note_token_start(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: remember where the current token starts so it can be
/// reconstructed from the input on error; current has already been
/// consumed, hence the -1
void note_token_start(std::true_type /*lazy*/) noexcept
{
token_string_start = ia.get_consumed_count() - 1;
}
/// streaming adapter: start copying the token eagerly, beginning with the
/// already-read first character
void note_token_start(std::false_type /*lazy*/) noexcept
{
token_string.clear();
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
@@ -8764,10 +8876,9 @@ scan_number_done:
current = ia.get_character();
}
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
// seekable adapters reconstruct the token lazily on error (see
// get_token_string), so the eager per-character copy is skipped
capture_char(std::integral_constant<bool, lazy_token_string> {});
if (current == '\n')
{
@@ -8778,6 +8889,18 @@ scan_number_done:
return current;
}
/// seekable adapter: nothing to capture, the token is rebuilt on error
void capture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: copy the scanned character into token_string
void capture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
token_string.push_back(char_traits<char_type>::to_char_type(current));
}
}
/*!
@brief unget current character (read it again on next get)
@@ -8805,6 +8928,15 @@ scan_number_done:
--position.chars_read_current_line;
}
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
}
/// seekable adapter: nothing was captured, so nothing to undo
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
/// streaming adapter: drop the character copied by the matching get()
void uncapture_char(std::false_type /*lazy*/)
{
if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))
{
JSON_ASSERT(!token_string.empty());
@@ -8862,14 +8994,38 @@ scan_number_done:
return position;
}
/// 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
{
// a pending unget of a real (non-EOF) character means that character
// was consumed from the input but is not part of the token; EOF is
// never consumed, so it must not be subtracted (mirrors unget())
const bool pending_real_unget = next_unget && current != char_traits<char_type>::eof();
const std::size_t stop = ia.get_consumed_count() - (pending_real_unget ? 1u : 0u);
if (JSON_HEDLEY_LIKELY(stop >= token_string_start))
{
ia.copy_consumed_range(token_string_start, stop, out);
}
return out;
}
/// streaming adapter: the token was copied eagerly while scanning
const std::vector<char_type>& collect_token_chars(std::vector<char_type>& /*out*/, std::false_type /*lazy*/) const
{
return token_string;
}
/// return the last read token (for errors only). Will never contain EOF
/// (an arbitrary value that is not a valid char value, often -1), because
/// 255 may legitimately occur. May contain NUL, which should be escaped.
std::string get_token_string() const
{
std::vector<char_type> reconstructed;
const std::vector<char_type>& chars = collect_token_chars(reconstructed, std::integral_constant<bool, lazy_token_string> {});
// escape control characters
std::string result;
for (const auto c : token_string)
for (const auto c : chars)
{
if (static_cast<unsigned char>(c) <= '\x1F')
{
@@ -9030,9 +9186,14 @@ scan_number_done:
/// the start position of the current token
position_t position {};
/// raw input token string (for error messages)
/// raw input token string for error messages; only populated for streaming
/// adapters (seekable adapters reconstruct it lazily via token_string_start)
std::vector<char_type> token_string {};
/// start offset of the current token within the input, used to reconstruct
/// the last read token on error for seekable adapters (see collect_token_chars)
std::size_t token_string_start = 0;
/// buffer for variable-length tokens (numbers, strings)
string_t token_buffer {};
@@ -13073,18 +13234,7 @@ class binary_reader
const NumberType len,
string_t& result)
{
bool success = true;
for (NumberType i = 0; i < len; i++)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "string")))
{
success = false;
break;
}
result.push_back(static_cast<typename string_t::value_type>(current));
}
return success;
return get_bytes(format, len, "string", result);
}
/*!
@@ -13106,18 +13256,66 @@ class binary_reader
const NumberType len,
binary_t& result)
{
bool success = true;
for (NumberType i = 0; i < len; i++)
return get_bytes(format, len, "binary", result);
}
/*!
@brief read @a len bytes from the input into a string or byte container
@tparam NumberType the type of the length
@tparam ContainerType the destination container (string_t or binary_t)
@param[in] format the current format (for diagnostics)
@param[in] len number of bytes to read
@param[in] context further context information (for diagnostics)
@param[out] result container the bytes are appended to
@return whether reading completed
@note We cannot reserve @a len bytes for the result up front, because
@a len may be far larger than the actual input. Instead we read in
bounded chunks, so the peak allocation is capped regardless of the
claimed length while the per-byte loop is replaced by block copies
(a std::memcpy for contiguous inputs). @ref unexpect_eof() still
detects a premature end of input.
*/
template<typename NumberType, typename ContainerType>
bool get_bytes(const input_format_t format,
NumberType len,
const char* context,
ContainerType& result)
{
// upper bound on the number of bytes read (and allocated) per chunk
constexpr std::size_t chunk_size = 4096;
while (len > 0)
{
get();
if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, "binary")))
// number of bytes to read this iteration: min(chunk_size, len),
// computed without truncating chunk_size to a narrow NumberType
const std::size_t wanted = (static_cast<std::uintmax_t>(len) < static_cast<std::uintmax_t>(chunk_size))
? static_cast<std::size_t>(len)
: chunk_size;
const std::size_t old_size = result.size();
result.resize(old_size + wanted);
// resize() is required to make size() exactly old_size + wanted;
// that is the room get_elements() is allowed to write into
JSON_ASSERT(result.size() == old_size + wanted);
const std::size_t bytes_read = ia.get_elements(&result[old_size], wanted);
chars_read += bytes_read;
if (JSON_HEDLEY_UNLIKELY(bytes_read < wanted))
{
success = false;
break;
// premature end of input: shrink to what was actually read and
// report the failure at the first missing byte (same position
// accounting as get_to() for partial number reads)
result.resize(old_size + bytes_read);
++chars_read;
current = char_traits<char_type>::eof();
return unexpect_eof(format, context);
}
result.push_back(static_cast<typename binary_t::value_type>(current));
// a full chunk was read; get_elements() never returns more than requested
JSON_ASSERT(bytes_read == wanted);
len = static_cast<NumberType>(len - static_cast<NumberType>(wanted));
}
return success;
return true;
}
/*!
@@ -14896,8 +15094,6 @@ NLOHMANN_JSON_NAMESPACE_END
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
/*!
@brief Default base class of the @ref basic_json class.
@@ -14908,13 +15104,27 @@ of @ref basic_json do not require complex case distinctions
@ref basic_json always has a base class.
By default, this class is used because it is empty and thus has no effect
on the behavior of @ref basic_json.
@note This class intentionally lives in namespace @ref nlohmann rather than
@ref nlohmann::detail. Every @ref basic_json specialization derives from
it (via @ref detail::json_base_class) unless a custom base class is
supplied, which makes its namespace an associated namespace of
@ref basic_json for the purpose of argument-dependent lookup (ADL). If
it lived in `nlohmann::detail`, that namespace - and with it the
library's internal `to_json`/`from_json` overloads - would leak into
ADL for any unqualified `to_json`/`from_json` call a user makes
involving a @ref basic_json argument, silently shadowing the user's own
overloads in some cases.
*/
struct json_default_base {};
namespace detail
{
template<class T>
using json_base_class = typename std::conditional <
std::is_same<T, void>::value,
json_default_base,
::nlohmann::json_default_base,
T
>::type;
+4 -1
View File
@@ -229,7 +229,10 @@ TEST_CASE("algorithms")
{
json j = {13, 29, 3, {{"one", 1}, {"two", 2}}, true, false, {1, 2, 3}, "foo", "baz", nullptr};
std::partial_sort(j.begin(), j.begin() + 4, j.end());
CHECK(j == json({nullptr, false, true, 3, {{"one", 1}, {"two", 2}}, 29, {1, 2, 3}, "foo", "baz", 13}));
// only the first four elements are expected to be sorted, the rest are
// unspecified by the standard
const json expected({nullptr, false, true, 3});
CHECK(std::equal(j.begin(), j.begin() + 4, begin(expected)));
}
}
+40
View File
@@ -2778,3 +2778,43 @@ TEST_CASE("Tagged values")
CHECK(!jb["binary"].get_binary().has_subtype());
}
}
TEST_CASE("CBOR large strings and binaries (chunked reader)")
{
// The binary reader reads strings and byte arrays in bounded chunks; make
// sure roundtripping is correct for lengths around and beyond the internal
// chunk size (4096 bytes), for both vector (iterator) and pointer inputs.
for (const std::size_t len :
{
std::size_t{0}, std::size_t{1}, std::size_t{4095}, std::size_t{4096},
std::size_t{4097}, std::size_t{8192}, std::size_t{100000}
})
{
CAPTURE(len);
// text string
const json j_string = std::string(len, 'x');
const std::vector<std::uint8_t> v_string = json::to_cbor(j_string);
CHECK(json::from_cbor(v_string) == j_string);
// pointer input exercises the std::memcpy fast path
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_string.data()),
reinterpret_cast<const char*>(v_string.data()) + v_string.size()) == j_string);
// byte string
const json j_binary = json::binary(std::vector<std::uint8_t>(len, 0xCD));
const std::vector<std::uint8_t> v_binary = json::to_cbor(j_binary);
CHECK(json::from_cbor(v_binary) == j_binary);
CHECK(json::from_cbor(reinterpret_cast<const char*>(v_binary.data()),
reinterpret_cast<const char*>(v_binary.data()) + v_binary.size()) == j_binary);
// a truncated payload must still be reported as an error, never crash
// or loop, regardless of the (large) announced length
if (len > 16)
{
std::vector<std::uint8_t> truncated = v_string;
truncated.resize(truncated.size() - 8);
json _;
CHECK_THROWS_AS(_ = json::from_cbor(truncated), json::parse_error);
}
}
}
+113
View File
@@ -16,6 +16,12 @@ using nlohmann::json;
#endif
#include <valarray>
#include <algorithm>
#include <list>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
namespace
{
@@ -1725,3 +1731,110 @@ TEST_CASE("parser class")
CHECK_THROWS_WITH_AS(_ = json::parse("/*", nullptr, true, true), "[json.exception.parse_error.101] parse error at line 1, column 3: syntax error while parsing value - invalid comment; missing closing '*/'; last read: '/*<U+0000>'", json::parse_error);
}
}
// this test relies on parse errors being thrown, so it is skipped when
// exceptions are disabled (json::parse aborts instead of throwing there)
#if !defined(JSON_NOEXCEPTION)
namespace
{
// Return the exception message from parsing @a input, or a "<no error ...>"
// sentinel if the parse unexpectedly succeeds. json::parse is nodiscard, so the
// result is consumed (via size()) to keep -Wunused-result / -Werror happy.
template<typename InputType>
std::string parse_error_message(InputType&& input)
{
try
{
const json j = json::parse(std::forward<InputType>(input));
return "<no error, size " + std::to_string(j.size()) + ">";
}
catch (const json::exception& e)
{
return e.what();
}
}
template<typename IteratorType>
std::string parse_error_message_range(IteratorType first, IteratorType last)
{
try
{
const json j = json::parse(first, last);
return "<no error, size " + std::to_string(j.size()) + ">";
}
catch (const json::exception& e)
{
return e.what();
}
}
} // namespace
TEST_CASE("last-read diagnostics are identical across input adapters")
{
// The lexer reconstructs the "last read" token lazily for seekable adapters
// (contiguous byte input) and copies it eagerly for streaming adapters.
// Both strategies must yield byte-for-byte identical error messages.
// a selection of malformed inputs that exercise different token kinds,
// whitespace/structural accumulation, number overflow, and control-char
// escaping in the reconstructed "last read" token
const std::vector<std::string> inputs =
{
"[1,2,x]",
" \n @",
"{\"a\": }",
"1.18973e+4932",
"\"\t\"",
"tru",
"[1 2]",
"\xEF\xBB\xBF nul",
};
for (const auto& s : inputs)
{
CAPTURE(s);
// reference: contiguous std::string -> seekable (lazy) path
const std::string reference = parse_error_message(s);
// every input is malformed, so parsing must fail (error messages start
// with '['; the success sentinel returned above starts with '<')
CHECK(reference.front() == '[');
// const char* -> also seekable
CHECK(parse_error_message(s.c_str()) == reference);
// std::vector<char> iterators -> seekable (random-access)
{
const std::vector<char> v(s.begin(), s.end());
CHECK(parse_error_message_range(v.begin(), v.end()) == reference);
}
// std::list iterators -> non-seekable (bidirectional) eager path
{
const std::list<char> l(s.begin(), s.end());
CHECK(parse_error_message_range(l.begin(), l.end()) == reference);
}
// std::istringstream -> non-seekable streaming eager path
{
std::istringstream ss(s);
CHECK(parse_error_message(ss) == reference);
}
// wide strings -> wide_string_input_adapter eager path; only comparable
// for ASCII input, as non-ASCII bytes are transcoded to different UTF-8
const bool is_ascii = std::all_of(s.begin(), s.end(), [](char c)
{
return static_cast<unsigned char>(c) < 0x80;
});
if (is_ascii)
{
const std::u16string w16(s.begin(), s.end());
CHECK(parse_error_message(w16) == reference);
const std::u32string w32(s.begin(), s.end());
CHECK(parse_error_message(w32) == reference);
}
}
}
#endif // !defined(JSON_NOEXCEPTION)
+96
View File
@@ -1358,4 +1358,100 @@ TEST_CASE("regression test #5122 - nlohmann::ordered_map move-assignment transfe
CHECK(src.begin()->first == "after-move");
}
// Stand-in for a third-party library (e.g., Eigen as of 3.4, which added
// STL-compatible begin()/end() to its vector types), living in its own
// namespace with its own to_json overload for its vector type.
namespace issue_4320_eigen
{
// "array-compatible" from the library's point of view (it has begin()/end()),
// but for which this (fake) third-party namespace provides its own to_json.
struct vector3
{
double v[3]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-use-default-member-init,modernize-use-default-member-init)
vector3(double x, double y, double z) : v{x, y, z} {} // NOLINT(hicpp-member-init,cppcoreguidelines-pro-type-member-init)
double x() const
{
return v[0];
}
double y() const
{
return v[1];
}
double z() const
{
return v[2];
}
double* begin()
{
return v;
}
double* end()
{
return v + 3;
}
const double* begin() const
{
return v;
}
const double* end() const
{
return v + 3;
}
};
inline void to_json(json& j, const vector3& v) // NOLINT(misc-use-internal-linkage)
{
j = {{"x", v.x()}, {"y", v.y()}, {"z", v.z()}};
}
} // namespace issue_4320_eigen
// The user's own namespace, using the (fake) Eigen type as an implementation
// detail behind a payload type that has nothing to do with vectors/arrays.
namespace issue_4320
{
// Publicly derives from issue_4320_eigen::vector3 but does *not* define its
// own to_json - it is only ever used as a temporary to reach the base
// class's to_json via ADL.
struct vector3_wrapper : issue_4320_eigen::vector3
{
using issue_4320_eigen::vector3::vector3;
};
struct payload
{
double x, y, z;
};
inline vector3_wrapper to_eigen(const payload& p) // NOLINT(misc-use-internal-linkage)
{
return {p.x, p.y, p.z};
}
inline void to_json(json& j, const payload& p) // NOLINT(misc-use-internal-linkage)
{
// Unqualified call, passing a *derived* vector3_wrapper: relies on ADL
// finding issue_4320_eigen::to_json(json&, const vector3&) through the
// vector3 base class, via a derived-to-base conversion. Must NOT resolve
// to the library's own generic array-compatible to_json (an exact-match
// template for vector3_wrapper, since it also has begin()/end()), which
// would serialize this as [x, y, z] instead of {"x":x, "y":y, "z":z}.
to_json(j, to_eigen(p));
}
} // namespace issue_4320
TEST_CASE("issue #4320 - custom base class must not leak nlohmann::detail into ADL")
{
// Before the fix, basic_json unconditionally derived from a type living in
// nlohmann::detail (json_default_base), which made nlohmann::detail an
// associated namespace of every basic_json for ADL purposes. That leaked
// the library's internal generic-array to_json overload into unqualified
// to_json() calls made from user code, silently bypassing user-defined
// to_json overloads reached via a derived-to-base conversion.
const issue_4320::payload p{1.0, 2.0, 3.0};
json j;
to_json(j, p);
CHECK(j == json({{"x", 1.0}, {"y", 2.0}, {"z", 3.0}}));
}
DOCTEST_CLANG_SUPPRESS_WARNING_POP
+7115 -5102
View File
File diff suppressed because it is too large Load Diff