mirror of
https://github.com/nlohmann/json.git
synced 2026-09-25 22:05:29 +01:00
Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f422b753cc | ||
|
|
95e9a5931c | ||
|
|
1e44262091 | ||
|
|
c60a0bc336 | ||
|
|
d19f7f5dce | ||
|
|
632a5812a8 | ||
|
|
ec4bdc398a | ||
|
|
a9ab2a62ba | ||
|
|
ad2c14b985 | ||
|
|
e1310ad43c | ||
|
|
465407f3ce | ||
|
|
02dd3e67f2 | ||
|
|
abbe52d6de | ||
|
|
98278dc3f6 | ||
|
|
6c8ea0a6d1 | ||
|
|
01b53c8c15 | ||
|
|
cc472af13f | ||
|
|
80bf54a5a2 | ||
|
|
aada27405d | ||
|
|
3901b223e5 | ||
|
|
f290b36ad2 | ||
|
|
4daca40d7b | ||
|
|
7c90ec2323 | ||
|
|
305ca7dadd | ||
|
|
2e91641de2 | ||
|
|
8699de3064 | ||
|
|
5f659c881a | ||
|
|
36c079149a | ||
|
|
770f62dda9 | ||
|
|
bc066c1838 | ||
|
|
918da64657 | ||
|
|
f3768d6868 | ||
|
|
56b3ee566c | ||
|
|
ed513715a8 | ||
|
|
f751547a81 | ||
|
|
06b0452189 | ||
|
|
1054b2097e | ||
|
|
f92024b317 | ||
|
|
0b20b7e622 | ||
|
|
d2c1a6a272 | ||
|
|
a2b19d6158 | ||
|
|
e485441123 | ||
|
|
e564136c22 | ||
|
|
663013ce64 | ||
|
|
c41152e620 | ||
|
|
502e9d66f6 | ||
|
|
e8e1ba0db9 | ||
|
|
29ba5973b6 | ||
|
|
75efd6b1c3 | ||
|
|
f58db1c9e8 | ||
|
|
ff65f688f7 | ||
|
|
af91eee2cc |
@@ -158,6 +158,15 @@ make amalgamate
|
||||
Running `make amalgamate` will also apply automatic formatting to the source files using
|
||||
[`Artistic Style`](https://astyle.sourceforge.net/). This formatting may modify your source files in-place. Be certain to review and commit any changes to avoid unintended formatting diffs in commits.
|
||||
|
||||
If you add, rename, or remove a header in `include/nlohmann`, also regenerate the header list in
|
||||
[`BUILD.bazel`](https://github.com/nlohmann/json/blob/develop/BUILD.bazel) (requires CMake) by executing:
|
||||
|
||||
```shell
|
||||
make BUILD.bazel
|
||||
```
|
||||
|
||||
The amalgamation check in CI fails if any of these generated files is out of date.
|
||||
|
||||
## Recommended documentation
|
||||
|
||||
- The library’s [README file](https://github.com/nlohmann/json/blob/master/README.md) is an excellent starting point to
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
- [ ] The changes are described in detail, both the what and why.
|
||||
- [ ] If applicable, an [existing issue](https://github.com/nlohmann/json/issues) is referenced.
|
||||
- [ ] If applicable, a fixed [OSS-Fuzz](https://issues.oss-fuzz.com) issue is referenced as `OSS-Fuzz: <id>` (see [fuzz testing](https://github.com/nlohmann/json/blob/develop/tests/fuzzing.md#handling-oss-fuzz-reports)).
|
||||
- [ ] The [Code coverage](https://coveralls.io/github/nlohmann/json) remained at 100%. A test case for every new line of code.
|
||||
- [ ] If applicable, the [documentation](https://json.nlohmann.me) is updated.
|
||||
- [ ] The source code is amalgamated by running `make amalgamate`.
|
||||
|
||||
+14
-3
@@ -9,12 +9,23 @@ identified a security vulnerability in this repository, please use the GitHub Se
|
||||
Until it is published, this draft security advisory will only be visible to the maintainers of this project. Other
|
||||
users and teams may be added once the advisory is created.
|
||||
|
||||
We will send a response indicating the next steps in handling your report. After the initial reply to your report, we
|
||||
will keep you informed of the progress towards a fix and full announcement and may ask for additional information or
|
||||
guidance.
|
||||
We will send a first response within 14 days, indicating the next steps in handling your report. After the initial
|
||||
reply to your report, we will keep you informed of the progress towards a fix and full announcement and may ask for
|
||||
additional information or guidance.
|
||||
|
||||
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
||||
|
||||
## Disclosure and credit
|
||||
|
||||
Once a fix is released, we publish the security advisory and list the fixed vulnerability in the release notes. We
|
||||
credit the reporter in both, unless they ask not to be named.
|
||||
|
||||
## Supported versions
|
||||
|
||||
Security fixes are made on the `develop` branch and shipped with the next release. Only the latest release receives
|
||||
security fixes; they are not backported to older releases. A release stops receiving security fixes when the next
|
||||
release is published, so please update to the latest release to get them.
|
||||
|
||||
## Unofficial packages
|
||||
|
||||
This project does not publish an official npm package. The npm package
|
||||
|
||||
@@ -29,6 +29,27 @@ labels:
|
||||
files:
|
||||
- ".github/external_ci/.*"
|
||||
|
||||
- label: "CI"
|
||||
files:
|
||||
- ".github/(dependabot|labeler)\\.yml"
|
||||
|
||||
- label: "aspect: binary formats"
|
||||
files:
|
||||
- "include/nlohmann/detail/input/binary_reader\\.hpp"
|
||||
- "include/nlohmann/detail/output/binary_writer\\.hpp"
|
||||
- "tests/src/unit-(bson|cbor|msgpack|ubjson|bjdata|binary_formats)"
|
||||
- "tests/src/fuzzer-parse_(bson|cbor|msgpack|ubjson|bjdata)"
|
||||
- "docs/mkdocs/docs/features/binary_formats/"
|
||||
- "docs/mkdocs/docs/(api/basic_json|examples)/(to|from)_(bson|cbor|msgpack|ubjson|bjdata)"
|
||||
|
||||
- label: "aspect: binary formats"
|
||||
title: "(?i)(bson|cbor|msgpack|messagepack|ubjson|bjdata|binary format)"
|
||||
|
||||
- label: "python"
|
||||
files:
|
||||
- "\\.py$"
|
||||
- "requirements[^/]*\\.txt$"
|
||||
|
||||
- label: "S"
|
||||
size-below: 10
|
||||
- label: "M"
|
||||
|
||||
@@ -3,6 +3,10 @@ name: "Check amalgamation"
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -57,13 +61,16 @@ jobs:
|
||||
python3 -mvenv venv
|
||||
venv/bin/pip3 install -r $MAIN_DIR/tools/astyle/requirements.txt
|
||||
|
||||
- name: Regenerate amalgamation and formatting
|
||||
- name: Regenerate amalgamation, formatting, and BUILD.bazel
|
||||
run: |
|
||||
cd $MAIN_DIR
|
||||
|
||||
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 .
|
||||
|
||||
# the header list of the Bazel "json" target must match the files in include/
|
||||
cmake -P cmake/scripts/gen_bazel_build_file.cmake
|
||||
|
||||
${{ github.workspace }}/venv/bin/astyle --project=tools/astyle/.astylerc --suffix=none --quiet \
|
||||
$INCLUDE_DIR/json.hpp $INCLUDE_DIR/json_fwd.hpp
|
||||
|
||||
@@ -87,7 +94,7 @@ jobs:
|
||||
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:"
|
||||
echo "The source code has not been amalgamated/formatted correctly or BUILD.bazel is out of date. Diff:"
|
||||
cat ${{ github.workspace }}/patch/amalgamation.patch
|
||||
echo "has_diff=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
name: CIFuzz
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
languages: c-cpp
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
|
||||
@@ -95,13 +95,13 @@ jobs:
|
||||
issue_number: issue_number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly.'
|
||||
body: '## 🔴 Amalgamation check failed! 🔴\nThe source code has not been amalgamated and/or formatted correctly, or `BUILD.bazel` is out of date.'
|
||||
+ (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).'
|
||||
+ '(https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#amalgamate-the-source-code).'
|
||||
: '')
|
||||
})
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
name: flawfinder
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -43,6 +47,6 @@ jobs:
|
||||
output: 'flawfinder_results.sarif'
|
||||
|
||||
- name: Upload analysis results to GitHub Security tab
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||
|
||||
@@ -4,6 +4,12 @@ on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
# pull_request_target runs on the base branch, so github.ref would put all pull
|
||||
# requests into one group; group by pull request number instead
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ on:
|
||||
push:
|
||||
branches: ["develop"]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -76,6 +80,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
@@ -19,6 +19,10 @@ on:
|
||||
schedule:
|
||||
- cron: '23 2 * * 4'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -61,7 +65,7 @@ jobs:
|
||||
|
||||
# Upload SARIF file generated in previous step
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
|
||||
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
if: always()
|
||||
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
container: ubuntu:focal
|
||||
strategy:
|
||||
matrix:
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_simdutf]
|
||||
target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local]
|
||||
steps:
|
||||
- name: Install build-essential
|
||||
run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev
|
||||
|
||||
@@ -124,11 +124,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Run CMake (Release)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Release'
|
||||
shell: pwsh
|
||||
- name: Run CMake (Debug)
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DJSON_FastTests=ON -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||
if: matrix.build_type == 'Debug'
|
||||
shell: pwsh
|
||||
- name: Build
|
||||
@@ -158,6 +158,10 @@ jobs:
|
||||
# to fit: IMAGE_REL_AMD64_SECREL against `.debug_line'" because the
|
||||
# MinGW linker cannot relocate the debug sections this test produces.
|
||||
# The tests are only built and run here, so the debug info is not used.
|
||||
# Do not add -O1 here to shrink the objects further: it does make them
|
||||
# link, but the binaries clang 11.0.1 and clang 18.1.8 then produce crash
|
||||
# before doctest prints its first line - 39 of 102 tests on clang 18.
|
||||
# Keep the objects small by splitting the test files instead.
|
||||
- name: Run CMake
|
||||
run: cmake -S . -B build ^
|
||||
-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" ^
|
||||
|
||||
+4
-1
@@ -30,8 +30,10 @@ cc_library(
|
||||
"include/nlohmann/detail/input/input_adapters.hpp",
|
||||
"include/nlohmann/detail/input/json_sax.hpp",
|
||||
"include/nlohmann/detail/input/lexer.hpp",
|
||||
"include/nlohmann/detail/input/number_parse.hpp",
|
||||
"include/nlohmann/detail/input/parser.hpp",
|
||||
"include/nlohmann/detail/input/position_t.hpp",
|
||||
"include/nlohmann/detail/input/string_scan.hpp",
|
||||
"include/nlohmann/detail/iterators/internal_iterator.hpp",
|
||||
"include/nlohmann/detail/iterators/iter_impl.hpp",
|
||||
"include/nlohmann/detail/iterators/iteration_proxy.hpp",
|
||||
@@ -49,12 +51,14 @@ cc_library(
|
||||
"include/nlohmann/detail/meta/detected.hpp",
|
||||
"include/nlohmann/detail/meta/identity_tag.hpp",
|
||||
"include/nlohmann/detail/meta/is_sax.hpp",
|
||||
"include/nlohmann/detail/meta/logic.hpp",
|
||||
"include/nlohmann/detail/meta/std_fs.hpp",
|
||||
"include/nlohmann/detail/meta/type_traits.hpp",
|
||||
"include/nlohmann/detail/meta/void_t.hpp",
|
||||
"include/nlohmann/detail/output/binary_writer.hpp",
|
||||
"include/nlohmann/detail/output/output_adapters.hpp",
|
||||
"include/nlohmann/detail/output/serializer.hpp",
|
||||
"include/nlohmann/detail/recursion_depth_limit.hpp",
|
||||
"include/nlohmann/detail/string_concat.hpp",
|
||||
"include/nlohmann/detail/string_escape.hpp",
|
||||
"include/nlohmann/detail/string_utils.hpp",
|
||||
@@ -67,7 +71,6 @@ cc_library(
|
||||
],
|
||||
includes = ["include"],
|
||||
visibility = ["//visibility:public"],
|
||||
alwayslink = True,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
|
||||
@@ -59,6 +59,7 @@ option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value compar
|
||||
option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT})
|
||||
option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON)
|
||||
option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF)
|
||||
option(JSON_StrictNulHandling "Build with strict NUL-byte handling enabled." OFF)
|
||||
|
||||
if (JSON_CI)
|
||||
include(ci)
|
||||
@@ -108,6 +109,10 @@ if (JSON_Diagnostics)
|
||||
message(STATUS "Diagnostics enabled (JSON_DIAGNOSTICS=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_StrictNulHandling)
|
||||
message(STATUS "Strict NUL-byte handling enabled (JSON_STRICT_NUL_HANDLING=1)")
|
||||
endif()
|
||||
|
||||
if (JSON_Diagnostic_Positions)
|
||||
message(STATUS "Diagnostic positions enabled (JSON_DIAGNOSTIC_POSITIONS=1)")
|
||||
endif()
|
||||
@@ -141,6 +146,7 @@ target_compile_definitions(
|
||||
$<$<BOOL:${JSON_Diagnostics}>:JSON_DIAGNOSTICS=1>
|
||||
$<$<BOOL:${JSON_Diagnostic_Positions}>:JSON_DIAGNOSTIC_POSITIONS=1>
|
||||
$<$<BOOL:${JSON_LegacyDiscardedValueComparison}>:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1>
|
||||
$<$<BOOL:${JSON_StrictNulHandling}>:JSON_STRICT_NUL_HANDLING=1>
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
@@ -250,12 +250,16 @@ Further documentation:
|
||||
|
||||
### `BUILD.bazel`
|
||||
|
||||
The file can be updated by calling
|
||||
The build definition for [Bazel](https://bazel.build). The file is generated by
|
||||
`cmake/scripts/gen_bazel_build_file.cmake`, which derives the header list from the files in `include`; change the
|
||||
script rather than editing the file by hand. The file can be updated by calling
|
||||
|
||||
```shell
|
||||
make BUILD.bazel
|
||||
```
|
||||
|
||||
The "Check amalgamation" workflow fails if the file is out of date.
|
||||
|
||||
### `meson.build`
|
||||
|
||||
The build definition for the [Meson](https://mesonbuild.com) build system.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef
|
||||
.PHONY: pretty clean ChangeLog.md release update_hedley update_hedley_undef BUILD.bazel
|
||||
|
||||
##########################################################################
|
||||
# configuration
|
||||
@@ -30,8 +30,9 @@ AMALGAMATED_FWD_FILE=single_include/nlohmann/json_fwd.hpp
|
||||
# main target
|
||||
all:
|
||||
@echo "amalgamate - amalgamate files single_include/nlohmann/json{,_fwd}.hpp from the include/nlohmann sources"
|
||||
@echo "BUILD.bazel - regenerate the Bazel BUILD file from the include/nlohmann sources"
|
||||
@echo "ChangeLog.md - generate ChangeLog file"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated"
|
||||
@echo "check-amalgamation - check whether sources have been amalgamated and BUILD.bazel is up to date"
|
||||
@echo "clean - remove built files"
|
||||
@echo "doctest - compile example files and check their output"
|
||||
@echo "fuzz_testing - prepare fuzz testing of the JSON parser"
|
||||
@@ -172,8 +173,13 @@ check-amalgamation:
|
||||
@diff $(AMALGAMATED_FWD_FILE) $(AMALGAMATED_FWD_FILE)~ || (echo "===================================================================\n Amalgamation required! Please read the contribution guidelines\n in file .github/CONTRIBUTING.md.\n===================================================================" ; mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE) ; false)
|
||||
@mv $(AMALGAMATED_FILE)~ $(AMALGAMATED_FILE)
|
||||
@mv $(AMALGAMATED_FWD_FILE)~ $(AMALGAMATED_FWD_FILE)
|
||||
@mv BUILD.bazel BUILD.bazel~
|
||||
@$(MAKE) BUILD.bazel
|
||||
@diff BUILD.bazel BUILD.bazel~ || (echo "===================================================================\n BUILD.bazel is out of date! Please run 'make BUILD.bazel'.\n===================================================================" ; mv BUILD.bazel~ BUILD.bazel ; false)
|
||||
@mv BUILD.bazel~ BUILD.bazel
|
||||
|
||||
BUILD.bazel: $(SRCS)
|
||||
# generate the Bazel BUILD file; phony, because a removed header would not trigger a rebuild
|
||||
BUILD.bazel:
|
||||
cmake -P cmake/scripts/gen_bazel_build_file.cmake
|
||||
|
||||
##########################################################################
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
[](https://github.com/nlohmann/json/releases)
|
||||
[](https://github.com/nlohmann/json/issues)
|
||||
[](https://isitmaintained.com/project/nlohmann/json "Average time to resolve an issue")
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/289)
|
||||
[](https://www.bestpractices.dev/projects/289)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/nlohmann/json)
|
||||
[](https://cloudback.it)
|
||||
[](https://github.com/sponsors/nlohmann)
|
||||
@@ -63,7 +63,7 @@ There are myriads of [JSON](https://json.org) libraries out there, and each may
|
||||
|
||||
- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings. The library is also included in all popular [package managers](https://json.nlohmann.me/integration/package_managers/).
|
||||
|
||||
- **Serious testing**. Our code is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289). See the [quality assurance](https://json.nlohmann.me/community/quality_assurance) overview documentation.
|
||||
- **Serious testing**. Our code is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](https://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [OpenSSF Best Practices](https://www.bestpractices.dev/projects/289). See the [quality assurance](https://json.nlohmann.me/community/quality_assurance) overview documentation.
|
||||
|
||||
Other aspects were not so important to us:
|
||||
|
||||
@@ -1421,7 +1421,7 @@ I deeply appreciate the help of the following people.
|
||||
6. [Joshua C. Randall](https://github.com/jrandall) fixed a bug in the floating-point serialization.
|
||||
7. [Aaron Burghardt](https://github.com/aburgh) implemented code to parse streams incrementally. Furthermore, he greatly improved the parser class by allowing the definition of a filter function to discard undesired elements while parsing.
|
||||
8. [Daniel Kopeček](https://github.com/dkopecek) fixed a bug in the compilation with GCC 5.0.
|
||||
9. [Florian Weber](https://github.com/Florianjw) fixed a bug in and improved the performance of the comparison operators.
|
||||
9. [Fiona Johanna Weber](https://github.com/Fiona-J-W) fixed a bug in and improved the performance of the comparison operators.
|
||||
10. [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping.
|
||||
11. [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums.
|
||||
12. [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual Studio.
|
||||
@@ -1523,14 +1523,14 @@ I deeply appreciate the help of the following people.
|
||||
108. [Kevin Tonon](https://github.com/ktonon) overworked the C++11 compiler checks in CMake.
|
||||
109. [Axel Huebl](https://github.com/ax3l) simplified a CMake check and added support for the [Spack package manager](https://spack.io).
|
||||
110. [Carlos O'Ryan](https://github.com/coryan) fixed a typo.
|
||||
111. [James Upjohn](https://github.com/jammehcow) fixed a version number in the compilers section.
|
||||
111. [James Upjohn](https://github.com/jupjohn) fixed a version number in the compilers section.
|
||||
112. [Chuck Atkins](https://github.com/chuckatkins) adjusted the CMake files to the CMake packaging guidelines and provided documentation for the CMake integration.
|
||||
113. [Jan Schöppach](https://github.com/dns13) fixed a typo.
|
||||
114. [martin-mfg](https://github.com/martin-mfg) fixed a typo.
|
||||
115. [Matthias Möller](https://github.com/TinyTinni) removed the dependency from `std::stringstream`.
|
||||
116. [agrianius](https://github.com/agrianius) added code to use alternative string implementations.
|
||||
117. [Daniel599](https://github.com/Daniel599) allowed to use more algorithms with the `items()` function.
|
||||
118. [Julius Rakow](https://github.com/jrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com).
|
||||
118. [Julius Rakow](https://github.com/juliusrakow) fixed the Meson include directory and fixed the links to [cppreference.com](https://cppreference.com).
|
||||
119. [Sonu Lohani](https://github.com/sonulohani) fixed the compilation with MSVC 2015 in debug mode.
|
||||
120. [grembo](https://github.com/grembo) fixed the test suite and re-enabled several test cases.
|
||||
121. [Hyeon Kim](https://github.com/simnalamburt) introduced the macro `JSON_INTERNAL_CATCH` to control the exception handling inside the library.
|
||||
@@ -1581,7 +1581,7 @@ I deeply appreciate the help of the following people.
|
||||
166. [Mark Beckwith](https://github.com/wythe) fixed a typo.
|
||||
167. [yann-morin-1998](https://github.com/yann-morin-1998) helped to reduce the CMake requirement to version 3.1.
|
||||
168. [Konstantin Podsvirov](https://github.com/podsvirov) maintains a package for the MSYS2 software distro.
|
||||
169. [remyabel](https://github.com/remyabel) added GNUInstallDirs to the CMake files.
|
||||
169. [remyabel](https://github.com/remyabel2) added GNUInstallDirs to the CMake files.
|
||||
170. [Taylor Howard](https://github.com/taylorhoward92) fixed a unit test.
|
||||
171. [Gabe Ron](https://github.com/Macr0Nerd) implemented the `to_string` method.
|
||||
172. [Watal M. Iwasaki](https://github.com/heavywatal) fixed a Clang warning.
|
||||
@@ -1608,7 +1608,7 @@ I deeply appreciate the help of the following people.
|
||||
193. [Hubert Chathi](https://github.com/uhoreg) made CMake's version config file architecture-independent.
|
||||
194. [OmnipotentEntity](https://github.com/OmnipotentEntity) implemented the binary values for CBOR, MessagePack, BSON, and UBJSON.
|
||||
195. [ArtemSarmini](https://github.com/ArtemSarmini) fixed a compilation issue with GCC 10 and fixed a leak.
|
||||
196. [Evgenii Sopov](https://github.com/sea-kg) integrated the library to the wsjcpp package manager.
|
||||
196. [Evgenii Sopov](https://github.com/sea5kg) integrated the library to the wsjcpp package manager.
|
||||
197. [Sergey Linev](https://github.com/linev) fixed a compiler warning.
|
||||
198. [Miguel Magalhães](https://github.com/magamig) fixed the year in the copyright.
|
||||
199. [Gareth Sylvester-Bradley](https://github.com/garethsb-sony) fixed a compilation issue with MSVC.
|
||||
@@ -1702,7 +1702,7 @@ I deeply appreciate the help of the following people.
|
||||
287. [NN](https://github.com/NN---) added the Visual Studio output directory to `.gitignore`.
|
||||
288. [Romain Reignier](https://github.com/romainreignier) improved the performance of the vector output adapter.
|
||||
289. [Mike](https://github.com/Mike-Leo-Smith) fixed the `std::iterator_traits`.
|
||||
290. [Richard Hozák](https://github.com/zxey) added macro `JSON_NO_ENUM` to disable default enum conversions.
|
||||
290. [Richard Hozák](https://github.com/richardhozak) added macro `JSON_NO_ENUM` to disable default enum conversions.
|
||||
291. [vakokako](https://github.com/vakokako) fixed tests when compiling with C++20.
|
||||
292. [Alexander “weej” Jones](https://github.com/alexweej) fixed an example in the README.
|
||||
293. [Eli Schwartz](https://github.com/eli-schwartz) added more files to the `include.zip` archive.
|
||||
@@ -1727,7 +1727,7 @@ I deeply appreciate the help of the following people.
|
||||
312. [Gareth Sylvester-Bradley](https://github.com/garethsb) added `operator/=` and `operator/` to construct JSON pointers.
|
||||
313. [Michael Macnair](https://github.com/mykter) added support for afl-fuzz testing.
|
||||
314. [Berkus Decker](https://github.com/berkus) fixed a typo in the README.
|
||||
315. [Illia Polishchuk](https://github.com/effolkronium) improved the CMake testing.
|
||||
315. [Illia Polishchuk](https://github.com/ilqvya) improved the CMake testing.
|
||||
316. [Ikko Ashimine](https://github.com/eltociear) fixed a typo.
|
||||
317. [Raphael Grimm](https://github.com/barcode) added the possibility to define a custom base class.
|
||||
318. [tocic](https://github.com/tocic) fixed typos in the documentation.
|
||||
@@ -1797,6 +1797,66 @@ I deeply appreciate the help of the following people.
|
||||
382. [bitFiedler](https://github.com/bitFiedler) made GDB pretty printer work with Python 3.8.
|
||||
383. [Gianfranco Costamagna](https://github.com/LocutusOfBorg) fixed a compiler warning.
|
||||
384. [risa2000](https://github.com/risa2000) made `std::filesystem::path` conversion to/from UTF-8 encoded string explicit.
|
||||
385. [AM](https://github.com/maqnouch) fixed typos in the README.
|
||||
386. [dmenendez-gruposantander](https://github.com/dmenendez-gruposantander) fixed typos in the comments of the examples.
|
||||
387. [Mihai Stan](https://github.com/mstan-xx) fixed comparisons against the literal `0`.
|
||||
388. [Matt Gumbel](https://github.com/intelmatt) fixed some `-Weffc++` warnings.
|
||||
389. [vimpunk](https://github.com/vimpunk) moved a lambda out of an unevaluated context to support older compilers.
|
||||
390. [Chris Harris](https://github.com/cjh1) fixed the compilation with GCC 4.8.
|
||||
391. [Palmer Dabbelt](https://github.com/palmer-dabbelt) generated and installed a pkg-config file.
|
||||
392. [Gus Pozuelo](https://github.com/ap-viavi) made `ordered_map` compatible with GCC 5.5, Clang 3.6, and Xcode 9.
|
||||
393. [AK](https://github.com/Lioncky) fixed an MSVC build error caused by the `min`/`max` macros from `windows.h`.
|
||||
394. [Sergiu Deitsch](https://github.com/sergiud) provided a fallback for missing `char8_t` support.
|
||||
395. [Xiaochuan Ye](https://github.com/XueSongTap) fixed `from_msgpack` for `std::byte` input by specializing `std::char_traits`.
|
||||
396. [Ville Vesilehto](https://github.com/thevilledev) fixed an overflow in the BJData size calculation and rejected overflowing negative integers in CBOR.
|
||||
397. [NmPassTHFan](https://github.com/nmpassthf) replaced the deprecated `std::is_trivial` for C++26.
|
||||
398. [Chris Ever](https://github.com/chirsz-ever) added the `ignore_trailing_commas` parser option.
|
||||
399. [Kuan-Fu Wu](https://github.com/kfwu1999) fixed the example code for `json_pointer` initialization.
|
||||
400. [David Kilzer](https://github.com/ddkilzer) added a missing header to the input adapters.
|
||||
401. [Miko](https://github.com/mikomikotaishi) added proper C++20 module support, simplified the module API, and fixed missing exports.
|
||||
402. [hitgirl](https://github.com/hitgil) fixed the CMake configuration when cross-compiling.
|
||||
403. [Devon Thomas](https://github.com/ThomaDevOSU) mentioned the Artistic Style formatting in the contribution guidelines.
|
||||
404. [Erik Hu](https://github.com/Erikhu1) made Coveralls upload errors non-fatal in the CI.
|
||||
405. [co63oc](https://github.com/co63oc) fixed typos.
|
||||
406. [DmitriBogdanov](https://github.com/DmitriBogdanov) fixed broken package manager links in the documentation.
|
||||
407. [Bander](https://github.com/banderzhm) improved the MSVC compatibility of the C++ modules.
|
||||
408. [Andy Choi](https://github.com/ccpong) removed an unnecessary `template` keyword before `get` in the README and the documentation.
|
||||
409. [SamareshSingh](https://github.com/ssam18) fixed single-element brace initialization to copy/move instead of wrapping in an array, fixed the `WITH_DEFAULT` macros for `ordered_map`, and handled moved events in `serve_header.py`.
|
||||
410. [Aditya](https://github.com/Lumowhisp) improved the documentation of the documentation generation.
|
||||
411. [cheese1](https://github.com/cheese1) clarified the README.
|
||||
412. [KhloodElhossiny](https://github.com/khloodelhossiny) enabled `std::string_view` keys in `operator[]`.
|
||||
413. [Charles Cabergs](https://github.com/cacharle) fixed a `-Wtautological-constant-out-of-range-compare` warning.
|
||||
414. [EALePain](https://github.com/EALePain) made the `std::tuple` conversion work with reference types such as `std::tie`.
|
||||
415. [koala_oishi](https://github.com/chibi-dogs) fixed grammatical wording in the README.
|
||||
416. [riccardoori11](https://github.com/riccardoori11) fixed a typo in the documentation.
|
||||
417. [Swastik Bose](https://github.com/VasuBhakt) fixed the parent pointers after `update()` with `JSON_DIAGNOSTICS` and fixed the Doxygen autolinking of requirements.
|
||||
418. [trdesilva](https://github.com/trdesilva) added `front`, `pop_front`, and `push_front` to `json_pointer`.
|
||||
419. [Akhilesh Arora](https://github.com/akhilesharora) fixed an incomplete-type error with `ordered_json`.
|
||||
420. [Hariom Phulre](https://github.com/hariomphulre) fixed the C++20 modules compilation with GCC.
|
||||
421. [Kirill Lokotkov](https://github.com/RUSLoker) fixed printing `long double` values.
|
||||
422. [George Sedov](https://github.com/radistmorse) added the `NLOHMANN_DEFINE_TYPE_*_WITH_NAMES` macros.
|
||||
423. [Caillin Nugent](https://github.com/nugentcaillin) added the `NLOHMANN_JSON_SERIALIZE_ENUM_STRICT` macro.
|
||||
424. [Cosmin D.](https://github.com/drcosmin) fixed `std::filesystem::path` conversions and added an MSVC workaround for `std::unique_ptr`.
|
||||
425. [Paul Dreik](https://github.com/pauldreik) fixed a test relying on implementation-specific behavior.
|
||||
426. [Daniel Falk](https://github.com/daniel-falk) added missing copyright notices to the SBOM.
|
||||
427. [Federico Sfriso](https://github.com/federicosfriso05-dotcom) added support for constructing JSON values from C++20 range views.
|
||||
428. [Luke Banicevic](https://github.com/banaboi) fixed corrupt BSON output for lengths exceeding `INT32_MAX`, cleaned up the BSON writer, and improved the documentation.
|
||||
429. [Patrick Armstrong](https://github.com/Patrick10199) updated the CBOR references and the half-precision float assertions.
|
||||
430. [Yash Bavadiya](https://github.com/xevrion) added checks to all BSON reads.
|
||||
431. [hum4nBeing](https://github.com/hum4nBeing) fixed the overflow handling of high-precision numbers in UBJSON.
|
||||
432. [tomatotomata](https://github.com/tomatotomata) added checks for reading CBOR tagged subtypes.
|
||||
433. [YingqiDuan](https://github.com/YingqiDuan) documented the BSON interoperability.
|
||||
434. [KBS](https://github.com/youdie006) documented the standards compliance and the strictness of `parse()` and `operator>>`.
|
||||
435. [Petr Bělohlávek](https://github.com/petrbel) added Clang 21 and 22 to the CI.
|
||||
436. [Dmitry Rantovov](https://github.com/darkdi) fixed the placement of a CBOR documentation block.
|
||||
437. [ljcjclljc](https://github.com/ljcjclljc) fixed the comparison of large unsigned integers with signed integers.
|
||||
438. [Sahil Kamate](https://github.com/sahilkamate03) fixed the handling of CBOR tags 0-5 and 21-23.
|
||||
439. [Krishnanand G](https://github.com/Krishnanand-G) made the UBJSON writer reject `use_type` without `use_size`.
|
||||
440. [whn](https://github.com/Whning0513) documented the lenient BSON input handling and corrected the complexity of `to_bson`.
|
||||
441. [elix3r](https://github.com/22elix3r) fixed `update()` with `merge_objects` when merging a primitive into an object.
|
||||
442. [Avionic Harshit](https://github.com/avionicharshit-byte) made `diff()` linear when an array shrinks.
|
||||
443. [Qatadaha Bin Matloob](https://github.com/qatcod) fixed comparisons between integers and floats and fixed unparsable BJData output.
|
||||
444. [Wu Shuwen](https://github.com/dajiaohuang) removed an unused include.
|
||||
|
||||
Thanks a lot for helping out! Please [let me know](mailto:mail@nlohmann.me) if I forgot someone.
|
||||
|
||||
|
||||
+64
-9
@@ -231,18 +231,20 @@ add_custom_target(ci_test_simdutf
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Enable brace-init copy semantics.
|
||||
# Enable strict NUL-byte handling.
|
||||
###############################################################################
|
||||
|
||||
add_custom_target(ci_test_brace_init_copy_semantics
|
||||
add_custom_target(ci_test_strict_nul_handling
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON
|
||||
-DCMAKE_CXX_FLAGS=-DJSON_BRACE_INIT_COPY_SEMANTICS=1
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_brace_init_copy_semantics && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test with brace-init copy semantics enabled"
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_StrictNulHandling=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_strict_nul_handling
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_strict_nul_handling
|
||||
# unit-testsuites contains a fixture (a "1e308" test value) that relies on the
|
||||
# legacy NUL-as-end-of-input behavior this macro disables; exclude it here, as
|
||||
# it is expected to fail under strict NUL handling and is out of scope for it
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_strict_nul_handling && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure -E "test-testsuites"
|
||||
COMMENT "Compile and test with strict NUL-byte handling enabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
@@ -260,6 +262,59 @@ add_custom_target(ci_test_noglobaludls
|
||||
COMMENT "Compile and test with global UDLs disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Disable enum serialization.
|
||||
###############################################################################
|
||||
|
||||
add_custom_target(ci_test_disableenumserialization
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableEnumSerialization=ON
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disableenumserialization
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disableenumserialization
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_disableenumserialization && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test with enum serialization disabled"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Skip the multiple-inclusion library version check.
|
||||
###############################################################################
|
||||
|
||||
# tests/src/skip_library_version_check.cpp deliberately simulates a scenario
|
||||
# (mixing two differently-versioned inclusions of the library in one
|
||||
# translation unit) that unavoidably triggers the compiler's own "macro
|
||||
# redefined" warning, so -- unlike the ci_test_* targets above -- it is
|
||||
# compiled directly here, with a modest warning set, instead of being folded
|
||||
# into the library's own -Weverything/-Werror unit test matrix.
|
||||
add_custom_target(ci_test_skiplibraryversioncheck
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/skip_library_version_check
|
||||
COMMAND ${CMAKE_CXX_COMPILER} -std=c++11 -Wall -Wextra
|
||||
-I${PROJECT_SOURCE_DIR}/include
|
||||
${PROJECT_SOURCE_DIR}/tests/src/skip_library_version_check.cpp
|
||||
-o ${PROJECT_BINARY_DIR}/skip_library_version_check/skip_library_version_check
|
||||
COMMAND ${PROJECT_BINARY_DIR}/skip_library_version_check/skip_library_version_check
|
||||
COMMENT "Compile and run a translation unit simulating a mismatched library version, with JSON_SKIP_LIBRARY_VERSION_CHECK defined"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Disable thread-local storage.
|
||||
###############################################################################
|
||||
|
||||
# Without thread-local storage, copying and comparing cannot bound their
|
||||
# descent and handle every object and array without the call stack. Those paths
|
||||
# are otherwise only reached by values nested deeper than the bound, so this
|
||||
# target is what runs the whole test suite through them.
|
||||
add_custom_target(ci_test_no_thread_local
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||
-DJSON_BuildTests=ON
|
||||
-DCMAKE_CXX_FLAGS=-DJSON_NO_THREAD_LOCAL
|
||||
-S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_no_thread_local
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_no_thread_local && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure
|
||||
COMMENT "Compile and test without thread-local storage"
|
||||
)
|
||||
|
||||
###############################################################################
|
||||
# Coverage.
|
||||
###############################################################################
|
||||
@@ -564,7 +619,7 @@ add_custom_target(ci_single_binaries
|
||||
add_custom_target(ci_benchmarks
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DCMAKE_BUILD_TYPE=Release -GNinja
|
||||
-S${PROJECT_SOURCE_DIR}/benchmarks -B${PROJECT_BINARY_DIR}/build_benchmarks
|
||||
-S${PROJECT_SOURCE_DIR}/tests/benchmarks -B${PROJECT_BINARY_DIR}/build_benchmarks
|
||||
COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_benchmarks --target json_benchmarks
|
||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_benchmarks && ./json_benchmarks
|
||||
COMMENT "Run benchmarks"
|
||||
|
||||
@@ -77,7 +77,7 @@ if(CMAKE_CROSSCOMPILING)
|
||||
endif()
|
||||
if(NOT DEFINED LIBCPP_VERSION_OUTPUT_CACHED)
|
||||
try_run(RUN_RESULT_VAR COMPILE_RESULT_VAR
|
||||
"${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_SOURCE_DIR}/cmake/detect_libcpp_version.cpp"
|
||||
"${CMAKE_BINARY_DIR}" SOURCES "${CMAKE_CURRENT_LIST_DIR}/detect_libcpp_version.cpp"
|
||||
RUN_OUTPUT_VARIABLE LIBCPP_VERSION_OUTPUT
|
||||
COMPILE_OUTPUT_VARIABLE LIBCPP_VERSION_COMPILE_OUTPUT
|
||||
)
|
||||
|
||||
@@ -1,24 +1,57 @@
|
||||
# generate Bazel BUILD file
|
||||
#
|
||||
# usage: cmake -P cmake/scripts/gen_bazel_build_file.cmake (or: make BUILD.bazel)
|
||||
#
|
||||
# The header list of the "json" target is derived from the files in include/. Everything else is fixed text below,
|
||||
# so edit this script rather than BUILD.bazel.
|
||||
|
||||
set(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
|
||||
get_filename_component(PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
|
||||
set(BUILD_FILE "${PROJECT_ROOT}/BUILD.bazel")
|
||||
|
||||
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "include/*.hpp")
|
||||
file(GLOB_RECURSE HEADERS LIST_DIRECTORIES false RELATIVE "${PROJECT_ROOT}" "${PROJECT_ROOT}/include/*.hpp")
|
||||
list(SORT HEADERS)
|
||||
|
||||
set(CONTENT [=[
|
||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||
load("@rules_license//rules:license.bzl", "license")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = [":license"],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
"LICENSE.MIT",
|
||||
])
|
||||
|
||||
license(
|
||||
name = "license",
|
||||
license_kinds = ["@rules_license//licenses/spdx:MIT"],
|
||||
license_text = "LICENSE.MIT",
|
||||
)
|
||||
|
||||
file(WRITE "${BUILD_FILE}" [=[
|
||||
cc_library(
|
||||
name = "json",
|
||||
hdrs = [
|
||||
]=])
|
||||
|
||||
foreach(header ${HEADERS})
|
||||
file(APPEND "${BUILD_FILE}" " \"${header}\",\n")
|
||||
string(APPEND CONTENT " \"${header}\",\n")
|
||||
endforeach()
|
||||
|
||||
file(APPEND "${BUILD_FILE}" [=[
|
||||
string(APPEND CONTENT [=[
|
||||
],
|
||||
includes = ["include"],
|
||||
visibility = ["//visibility:public"],
|
||||
alwayslink = True,
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "singleheader-json",
|
||||
hdrs = [
|
||||
"single_include/nlohmann/json.hpp",
|
||||
],
|
||||
includes = ["single_include"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
]=])
|
||||
|
||||
file(WRITE "${BUILD_FILE}" "${CONTENT}")
|
||||
|
||||
@@ -90,6 +90,10 @@ Linear in the length of the input. The parser is a predictive LL(1) parser.
|
||||
|
||||
A UTF-8 byte order mark is silently ignored.
|
||||
|
||||
By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input, rather than as an ordinary (and,
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
@@ -111,6 +115,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- [parse](parse.md) - deserialize from a compatible input
|
||||
- [sax_parse](sax_parse.md) - parse input using the SAX interface
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -120,6 +126,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ This overload is chosen if:
|
||||
- `ValueType` is not `basic_json`,
|
||||
- `json_serializer<ValueType>` has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`
|
||||
|
||||
`v` must not be `const`. Passing a `const` object is a compile-time error. For types such as arithmetic types, enums,
|
||||
and C arrays, the error is a `static_assert` that names the problem. For other types, the overload is not viable, and
|
||||
the compiler reports that no matching `get_to` was found.
|
||||
|
||||
## Template parameters
|
||||
|
||||
`ValueType`
|
||||
@@ -67,3 +71,4 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
|
||||
## Version history
|
||||
|
||||
- Since version 3.3.0.
|
||||
- Added a `static_assert` with a clear message for `const` arguments in version 3.13.0.
|
||||
|
||||
@@ -88,6 +88,8 @@ Strong exception safety: if an exception occurs, the original value stays intact
|
||||
do not belong to the same JSON value; example: `"iterators do not fit"`
|
||||
- Throws [`invalid_iterator.211`](../../home/exceptions.md#jsonexceptioninvalid_iterator211) if `first` or `last`
|
||||
are iterators into container for which insert is called; example: `"passed iterators may not belong to container"`
|
||||
- Throws [`invalid_iterator.202`](../../home/exceptions.md#jsonexceptioninvalid_iterator202) if `first` or `last`
|
||||
do not point to an array; example: `"iterators first and last must point to arrays"`
|
||||
4. The function can throw the following exceptions:
|
||||
- Throws [`type_error.309`](../../home/exceptions.md#jsonexceptiontype_error309) if called on JSON values other than
|
||||
arrays; example: `"cannot use insert() with string"`
|
||||
|
||||
@@ -103,6 +103,10 @@ A UTF-8 byte order mark is silently ignored.
|
||||
Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
[`parse_error.101`](../../home/exceptions.md#jsonexceptionparse_error101) with a detailed message.
|
||||
|
||||
By default, a `'\0'` (NUL) byte anywhere in the input is treated as end of input, rather than as an ordinary (and,
|
||||
outside of a string, invalid) byte; see the [FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for details and the
|
||||
[`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) macro to opt into rejecting it instead.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Parsing from a character array"
|
||||
@@ -236,6 +240,8 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
- [accept](accept.md) - check if the input is valid JSON
|
||||
- [sax_parse](sax_parse.md) - parse input using the SAX interface
|
||||
- [operator>>](../operator_gtgt.md) - deserialize from stream
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
|
||||
## Version history
|
||||
|
||||
@@ -246,6 +252,8 @@ Invalid Unicode escapes and unpaired surrogates in the input are reported as
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -69,7 +69,9 @@ The SAX event lister must follow the interface of [`json_sax`](../json_sax/index
|
||||
[`input_format_t`](input_format_t.md) for more information
|
||||
|
||||
`strict` (in)
|
||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default)
|
||||
: whether the input has to be consumed completely (optional, `#!cpp true` by default); when `#!cpp false` and the
|
||||
input is a `#!cpp std::istream`, the character that terminates a number is consumed unless
|
||||
[`JSON_PRECISE_STREAM_POSITION`](../macros/json_precise_stream_position.md) is defined to `1`; see [`operator>>`](../operator_gtgt.md#notes)
|
||||
|
||||
`ignore_comments` (in)
|
||||
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
||||
@@ -136,6 +138,8 @@ A UTF-8 byte order mark is silently ignored.
|
||||
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave a `#!cpp std::istream` positioned right
|
||||
after the parsed value when `strict` is `#!cpp false`.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
|
||||
@@ -46,9 +46,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
||||
|
||||
## Complexity
|
||||
|
||||
Proportional to the size of the JSON value `j` multiplied by its maximum nesting
|
||||
depth, `O(n × d)`. BSON length prefixes are computed recursively before nested
|
||||
values are written.
|
||||
Linear in the size of the JSON value `j`. The length prefixes of all nested documents and arrays are computed in one
|
||||
pass before anything is written.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -77,3 +76,4 @@ values are written.
|
||||
## Version history
|
||||
|
||||
- Added in version 3.4.0.
|
||||
- Linear in the size of `j`, and no longer limited by the call stack for deeply nested values, since version 3.13.0.
|
||||
|
||||
@@ -14,6 +14,13 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
- [**JSON_DIAGNOSTIC_POSITIONS**](json_diagnostic_positions.md) - access positions of elements
|
||||
- [**JSON_NOEXCEPTION**](json_noexception.md) - switch off exceptions
|
||||
|
||||
## Parsing
|
||||
|
||||
- [**JSON_PRECISE_STREAM_POSITION**](json_precise_stream_position.md) - opt in to leaving an input stream positioned
|
||||
right after a parsed number
|
||||
- [**JSON_STRICT_NUL_HANDLING**](json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input instead of
|
||||
treating it as end of input
|
||||
|
||||
## Language support
|
||||
|
||||
- [**JSON_HAS_CPP_11**<br>**JSON_HAS_CPP_14**<br>**JSON_HAS_CPP_17**<br>**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard
|
||||
@@ -22,6 +29,7 @@ header. See also the [macro overview page](../../features/macros.md).
|
||||
- [**JSON_HAS_STD_FORMAT**](json_has_std_format.md) - control `std::format`/`std::formatter` support
|
||||
- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support
|
||||
- [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers
|
||||
- [**JSON_NO_THREAD_LOCAL**](json_no_thread_local.md) - switch off the use of `thread_local` storage
|
||||
- [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers
|
||||
- [**JSON_USE_GLOBAL_UDLS**](json_use_global_udls.md) - place user-defined string literals (UDLs) into the global namespace
|
||||
- [**JSON_USE_SIMDUTF**](json_use_simdutf.md) - use the simdutf library to accelerate UTF-8 validation
|
||||
|
||||
@@ -38,6 +38,28 @@ The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no effect.
|
||||
|
||||
!!! warning "Applies to every single-element list"
|
||||
|
||||
The macro does not only affect a single JSON value in braces. **Any** single-element braced list is treated as its
|
||||
element, so it no longer creates a one-element array:
|
||||
|
||||
```cpp
|
||||
json j1 = {1}; // 1, not [1]
|
||||
json j2 = {"text"}; // "text", not ["text"]
|
||||
json j3 = {{1, 2}}; // [1,2], not [[1,2]]
|
||||
```
|
||||
|
||||
Code that relies on these producing arrays must use `json::array()` instead (see below). Lists with more than one
|
||||
element, and a single `[string, value]` pair such as `{{"key", "value"}}`, which still creates an object, are not
|
||||
affected. The library's own conversions are not affected either: for example, `std::tuple<int>{5}` still becomes
|
||||
`[5]`.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_bics`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
To explicitly create a single-element array without enabling this macro, use `json::array()`:
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# JSON_NO_THREAD_LOCAL
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_THREAD_LOCAL
|
||||
```
|
||||
|
||||
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
|
||||
toolchain does not support it.
|
||||
|
||||
Copying a value and comparing two values both descend into the first levels by letting the containers copy or compare
|
||||
themselves, and finish whatever is nested deeper than that without the call stack, so that neither can exhaust the stack
|
||||
however deeply the values are nested. Each counts the levels it has descended into in a `#!cpp thread_local` variable, as
|
||||
a counter shared between threads would be raced.
|
||||
|
||||
Without those counters, no descent can be bounded safely, so objects and arrays are copied and compared without the call
|
||||
stack right away. Both keep working exactly as they do otherwise - the same values come out, the same comparisons hold,
|
||||
and deeply nested values are handled just as safely - but both are slower, because the containers no longer copy or
|
||||
compare themselves. Copying the benchmark documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer, and
|
||||
comparing two equal ones 10% (`citm_catalog.json`) to 90% (`canada.json`) longer.
|
||||
|
||||
## Default definition
|
||||
|
||||
By default, `#!cpp JSON_NO_THREAD_LOCAL` is not defined.
|
||||
|
||||
```cpp
|
||||
#undef JSON_NO_THREAD_LOCAL
|
||||
```
|
||||
|
||||
The library defines it by itself for Clang targeting MinGW, which does not survive the `#!cpp thread_local` storage:
|
||||
copying a value segfaults there, with both old and current Clang versions, while GCC targeting MinGW is unaffected.
|
||||
Copying and comparing fall back to working without the call stack there, as they do whenever the macro is defined.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example
|
||||
|
||||
The code below forces the library not to use `#!cpp thread_local` storage.
|
||||
|
||||
```cpp
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.12.1.
|
||||
@@ -0,0 +1,131 @@
|
||||
# JSON_PRECISE_STREAM_POSITION
|
||||
|
||||
```cpp
|
||||
#define JSON_PRECISE_STREAM_POSITION /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, [`operator>>`](../operator_gtgt.md) and [`sax_parse`](../basic_json/sax_parse.md) with
|
||||
`strict = false` leave a `#!cpp std::istream` positioned right after the parsed value for every value type. By default,
|
||||
the character that terminates a number is consumed as well.
|
||||
|
||||
The macro only affects reading from a `#!cpp std::istream` when the rest of the stream is not required to be consumed.
|
||||
[`parse`](../basic_json/parse.md), [`accept`](../basic_json/accept.md), and all other inputs (strings, iterators,
|
||||
containers, `#!cpp FILE*`) are never affected.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
```cpp
|
||||
#define JSON_PRECISE_STREAM_POSITION 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
A number is the only JSON value whose end can be detected solely by reading the character that follows it. By
|
||||
default, that character is consumed and not put back, so the stream is left one byte too far after a number, and
|
||||
only after a number:
|
||||
|
||||
```cpp
|
||||
std::istringstream input("1true");
|
||||
json j;
|
||||
input >> j; // j == 1, but the stream now starts at "rue"
|
||||
```
|
||||
|
||||
With this macro, the character is only looked at and left in the stream, so the stream starts at `true`. This
|
||||
does not require the stream buffer to support putting a character back.
|
||||
|
||||
This was not changed unconditionally, because code can depend on the consumed character, even unknowingly (see
|
||||
[#5340](https://github.com/nlohmann/json/issues/5340)). Both of the following work by default only because the
|
||||
character after each number is swallowed, and behave differently with this macro:
|
||||
|
||||
```cpp
|
||||
std::istringstream input("1,2,3");
|
||||
json j1, j2, j3;
|
||||
input >> j1 >> j2 >> j3; // default: 1, 2, 3
|
||||
// with the macro: throws parse_error.101 at the ','
|
||||
```
|
||||
|
||||
```cpp
|
||||
std::istringstream input("42\nfoo");
|
||||
json j;
|
||||
std::string line;
|
||||
input >> j;
|
||||
std::getline(input, line); // default: "foo"
|
||||
// with the macro: "" (like after reading an int with >>)
|
||||
```
|
||||
|
||||
In both cases, the behavior with the macro is what you already get today when the value is not a number: `"a","b"`
|
||||
fails at the `,`, and `std::getline` after `{}` returns an empty string. This macro offers an opt-in path to
|
||||
the consistent behavior ahead of version 4.0.0, where it is planned to become the default.
|
||||
|
||||
!!! warning "Opt-in only"
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
|
||||
effect.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_psp`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
Separate the values in the stream with whitespace. The character consumed after a number is then the separator,
|
||||
and whitespace before the next value is skipped anyway.
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Default behavior (macro not defined)"
|
||||
|
||||
Without the macro, the character after a number is consumed:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input("1true");
|
||||
json j1, j2;
|
||||
input >> j1; // j1 == 1
|
||||
input >> j2; // throws parse_error.101: the stream now starts at "rue"
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Opt-in precise stream position (macro defined to 1)"
|
||||
|
||||
With the macro, the stream is positioned right after the number:
|
||||
|
||||
```cpp
|
||||
#define JSON_PRECISE_STREAM_POSITION 1
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream input("1true");
|
||||
json j1, j2;
|
||||
input >> j1; // j1 == 1
|
||||
input >> j2; // j2 == true
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
- [**sax_parse**](../basic_json/sax_parse.md) - generate SAX events
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
- Planned to become the default (with the macro removed) in version 4.0.0.
|
||||
@@ -0,0 +1,132 @@
|
||||
# JSON_STRICT_NUL_HANDLING
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING /* value */
|
||||
```
|
||||
|
||||
When defined to `1`, a `'\0'` (NUL) byte in JSON text input is rejected with `parse_error.101`, like any other
|
||||
unexpected byte, instead of being silently treated as end of input.
|
||||
|
||||
The macro only affects the JSON text parser ([`parse`](../basic_json/parse.md), [`accept`](../basic_json/accept.md),
|
||||
[`sax_parse`](../basic_json/sax_parse.md), and [`operator>>`](../operator_gtgt.md)). There are three cases where a NUL
|
||||
byte is still not rejected:
|
||||
|
||||
- The binary formats ([`from_bjdata`](../basic_json/from_bjdata.md), [`from_bson`](../basic_json/from_bson.md),
|
||||
[`from_cbor`](../basic_json/from_cbor.md), [`from_msgpack`](../basic_json/from_msgpack.md),
|
||||
[`from_ubjson`](../basic_json/from_ubjson.md)) are never affected: there, `0x00` is ordinary data.
|
||||
- A bare `const char*` pointer has no length of its own, so its length is still determined with `strlen()`. The first
|
||||
NUL byte therefore still marks the end of the input, and nothing after it is read.
|
||||
- One trailing `'\0'` at the end of a `char` array (e.g., a string literal) is trimmed; see the warning below.
|
||||
|
||||
## Default definition
|
||||
|
||||
The default value is `0` (disabled — existing behavior is preserved).
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
!!! note "Background"
|
||||
|
||||
By default, a `'\0'` byte anywhere in the input is treated the same as the real end of the input, rather than as
|
||||
an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored,
|
||||
without a parse error - including further, otherwise well-formed JSON:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("123") + '\0'); // == 123, no error
|
||||
json::parse(std::string("123") + '\0' + "true"); // == 123, the "true" is silently ignored too
|
||||
```
|
||||
|
||||
This falls out of the same convention used when no explicit input length is given at all: parsing from a
|
||||
`const char*` already stops at the first NUL byte via `strlen()`, since a bare pointer has no length of its own.
|
||||
The library applies that same NUL-terminated-C-string convention uniformly, rather than only when a length is
|
||||
genuinely unavailable - so a `std::string`, iterator range, or container whose content happens to include a NUL
|
||||
byte is affected the same way a raw `const char*` would be (see the
|
||||
[FAQ entry](../../home/faq.md#nul-bytes-in-the-input) for a fuller explanation).
|
||||
|
||||
This was not fixed unconditionally, because doing so is backwards-incompatible for any caller who happens to
|
||||
depend on the current behavior - even unknowingly, for instance because their input already contains trailing
|
||||
padding they never noticed was being discarded (see [#5530](https://github.com/nlohmann/json/issues/5530)).
|
||||
This macro instead offers an opt-in path to the corrected behavior ahead of version 4.0.0, where it is planned to
|
||||
become the default.
|
||||
|
||||
!!! warning "Opt-in only"
|
||||
|
||||
This macro must be defined **before** including `<nlohmann/json.hpp>`. Defining it after the include has no
|
||||
effect.
|
||||
|
||||
Enabling it also changes how a `char` array (including a string literal, e.g. `json::parse("123")`) is read: such
|
||||
an array normally carries a trailing `'\0'` contributed by the compiler, not by the source text. With this macro
|
||||
enabled, that one trailing byte is trimmed if present so that parsing a string literal keeps working; every other
|
||||
byte in the array - including any `'\0'` that is not the very last element - is read as real data and rejected
|
||||
like any other unexpected byte. Arrays of any other element type (`unsigned char`, `std::uint8_t`, ...), as used
|
||||
for CBOR or MessagePack, are never affected by this trimming; their full extent - including a genuine trailing
|
||||
`0x00` - is always preserved, in both states of this macro.
|
||||
|
||||
!!! note "ABI compatibility"
|
||||
|
||||
The value of this macro is encoded in the [namespace](../../features/namespace.md) (tag `_snul`), resulting in
|
||||
distinct symbol names. Translation units compiled with and without it can therefore be linked into the same program
|
||||
without One Definition Rule (ODR) violations, but they cannot exchange instances of library types.
|
||||
|
||||
!!! tip "Workaround without the macro"
|
||||
|
||||
To reject a NUL byte without enabling this macro, trim your input yourself before calling `parse()`:
|
||||
|
||||
```cpp
|
||||
s.resize(s.find('\0')); // drop everything from the first NUL onward, if any
|
||||
json::parse(s);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
??? example "Default behavior (macro not defined)"
|
||||
|
||||
Without the macro, a NUL byte silently ends parsing at that point:
|
||||
|
||||
```cpp
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = json::parse(std::string("123") + '\0' + "true");
|
||||
// j is 123 -- the '\0' and everything after it is silently ignored
|
||||
}
|
||||
```
|
||||
|
||||
??? example "Opt-in strict handling (macro defined to 1)"
|
||||
|
||||
With the macro, a NUL byte is rejected like any other unexpected byte:
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
int main()
|
||||
{
|
||||
json j = json::parse(std::string("123") + '\0' + "true");
|
||||
// throws parse_error.101 -- the NUL byte is now invalid input,
|
||||
// exactly like any other unexpected trailing byte
|
||||
|
||||
json ok = json::parse("123");
|
||||
// ok is 123 -- parsing from a string literal still works
|
||||
}
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
- [FAQ: NUL bytes in the input](../../home/faq.md#nul-bytes-in-the-input)
|
||||
- [**parse**](../basic_json/parse.md) - deserialize from a compatible input
|
||||
- [**accept**](../basic_json/accept.md) - check if the input is valid JSON
|
||||
- [**operator>>**](../operator_gtgt.md) - deserialize from stream
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 3.13.0.
|
||||
- Planned to become the default (with the macro removed) in version 4.0.0.
|
||||
@@ -24,6 +24,14 @@ By default, implicit conversions are enabled.
|
||||
You can prepare existing code by already defining `JSON_USE_IMPLICIT_CONVERSIONS` to `0` and replace any implicit
|
||||
conversions with calls to [`get`](../basic_json/get.md).
|
||||
|
||||
!!! tip "Automatic migration"
|
||||
|
||||
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites implicit
|
||||
conversions into explicit calls to [`get`](../basic_json/get.md); for example, `#!cpp int i = j;` becomes
|
||||
`#!cpp int i = j.get<int>();`. The check is not part of clang-tidy itself, and it does not catch every case (for
|
||||
example, constructing a `std::optional` from a JSON value), so review the result. See
|
||||
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
|
||||
|
||||
!!! hint "CMake option"
|
||||
|
||||
Implicit conversions can also be controlled with the CMake option
|
||||
|
||||
@@ -57,7 +57,8 @@ Summary:
|
||||
: name of the base type (class, struct) `type` is derived from
|
||||
|
||||
`member` (in)
|
||||
: name of the member variable to serialize/deserialize; up to 63 members can be given as a comma-separated list
|
||||
: name of the member variable to serialize/deserialize; up to 63 members can be given as a comma-separated
|
||||
list, which may also be empty
|
||||
|
||||
## Default definition
|
||||
|
||||
@@ -127,6 +128,20 @@ void to_json(BasicJsonType& j, const B& b) {
|
||||
- Macros 4, 5, and 6 have the same prerequisites of [NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE](nlohmann_define_type_non_intrusive.md).
|
||||
- Serialization/deserialization of base types must be defined.
|
||||
|
||||
!!! info "Derived types without own members"
|
||||
|
||||
The member list may be empty. The macro then generates a `to_json`/`from_json` pair that only delegates to
|
||||
the base type, so `type` serializes exactly like `base_type`:
|
||||
|
||||
```cpp
|
||||
struct derived : base
|
||||
{
|
||||
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(derived, base)
|
||||
};
|
||||
```
|
||||
|
||||
The `WITH_NAMES` variants do not support this.
|
||||
|
||||
!!! warning "Implementation limits"
|
||||
|
||||
See Implementation limits for [NLOHMANN_DEFINE_TYPE_INTRUSIVE](nlohmann_define_type_intrusive.md) and
|
||||
|
||||
@@ -33,7 +33,8 @@ Summary:
|
||||
: name of the type (class, struct) to serialize/deserialize
|
||||
|
||||
`member` (in)
|
||||
: name of the member variable to serialize/deserialize; up to 63 members can be given as a comma-separated list
|
||||
: name of the member variable to serialize/deserialize; up to 63 members can be given as a comma-separated
|
||||
list, which may also be empty
|
||||
|
||||
## Default definition
|
||||
|
||||
@@ -58,6 +59,20 @@ See the examples below for the concrete generated code.
|
||||
|
||||
[GetNonDefNonCopy]: ../../features/arbitrary_types.md#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
|
||||
|
||||
!!! info "Types without members"
|
||||
|
||||
The member list may be empty. The macro then generates a `to_json` that produces an empty JSON object
|
||||
`#!json {}`, and a `from_json` that reads no members:
|
||||
|
||||
```cpp
|
||||
struct marker
|
||||
{
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(marker)
|
||||
};
|
||||
```
|
||||
|
||||
The `WITH_NAMES` variants do not support this.
|
||||
|
||||
!!! warning "Implementation limits"
|
||||
|
||||
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
|
||||
|
||||
@@ -33,7 +33,8 @@ Summary:
|
||||
: name of the type (class, struct) to serialize/deserialize
|
||||
|
||||
`member` (in)
|
||||
: name of the (public) member variable to serialize/deserialize; up to 63 members can be given as a comma-separated list
|
||||
: name of the (public) member variable to serialize/deserialize; up to 63 members can be given as a
|
||||
comma-separated list, which may also be empty
|
||||
|
||||
## Default definition
|
||||
|
||||
@@ -59,6 +60,18 @@ See the examples below for the concrete generated code.
|
||||
|
||||
[GetNonDefNonCopy]: ../../features/arbitrary_types.md#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
|
||||
|
||||
!!! info "Types without members"
|
||||
|
||||
The member list may be empty. The macro then generates a `to_json` that produces an empty JSON object
|
||||
`#!json {}`, and a `from_json` that reads no members:
|
||||
|
||||
```cpp
|
||||
struct marker {};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(marker)
|
||||
```
|
||||
|
||||
The `WITH_NAMES` variants do not support this.
|
||||
|
||||
!!! warning "Implementation limits"
|
||||
|
||||
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
|
||||
|
||||
@@ -67,11 +67,20 @@ input >> j2; // parses the next value
|
||||
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
|
||||
`truefalse`, `[1][2]`, `{"a":1}{"b":2}`, and `"a""b"` can be read back to back without a separator.
|
||||
|
||||
This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
|
||||
Define [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) to `1` to leave the terminating character in the stream
|
||||
instead, so that the stream is positioned right after the value for every value type and no separator is
|
||||
needed. This is tracked in [#5340](https://github.com/nlohmann/json/issues/5340).
|
||||
|
||||
Note that reading concatenated values does **not** work for [JSON Lines](../features/parsing/json_lines.md)
|
||||
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||
|
||||
By default, a `'\0'` (NUL) byte encountered while reading a value is treated as end of input, rather than as an
|
||||
ordinary (and, outside of a string, invalid) byte; see the [FAQ entry](../home/faq.md#nul-bytes-in-the-input) for
|
||||
details and the [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) macro to opt into rejecting it
|
||||
instead. Because `operator>>` only parses a single value and does not require the rest of the stream to be consumed,
|
||||
a NUL byte *after* a complete value has no effect on `operator>>` either way; it only matters while a value is still
|
||||
being read.
|
||||
|
||||
!!! warning "Deprecation"
|
||||
|
||||
This function replaces function `#!cpp std::istream& operator<<(basic_json& j, std::istream& i)` which has
|
||||
@@ -98,7 +107,14 @@ Note that reading concatenated values does **not** work for [JSON Lines](../feat
|
||||
|
||||
- [accept](basic_json/accept.md) - check if the input is valid JSON
|
||||
- [parse](basic_json/parse.md) - deserialize from a compatible input
|
||||
- [`JSON_STRICT_NUL_HANDLING`](macros/json_strict_nul_handling.md) - opt in to rejecting a NUL byte in the input
|
||||
instead of treating it as end of input
|
||||
- [`JSON_PRECISE_STREAM_POSITION`](macros/json_precise_stream_position.md) - opt in to leaving the stream positioned right after a number
|
||||
|
||||
## Version history
|
||||
|
||||
- Added in version 1.0.0.
|
||||
- `JSON_STRICT_NUL_HANDLING` added in version 3.13.0 to optionally reject a NUL byte in the input instead of treating
|
||||
it as end of input; planned to become the default in version 4.0.0.
|
||||
- `JSON_PRECISE_STREAM_POSITION` added in version 3.13.0 to optionally leave the character that terminates a number in
|
||||
the stream; planned to become the default in version 4.0.0.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Assurance case
|
||||
|
||||
This page argues why the library meets its security requirements. It describes the threats the library faces, where the
|
||||
trust boundaries lie, and how the library's design and the [quality assurance](quality_assurance.md) counter these
|
||||
threats. To report a vulnerability, see the [security policy](security_policy.md).
|
||||
|
||||
## Threat model
|
||||
|
||||
The library parses, stores, and serializes JSON values in memory. It does not open network connections, does not open
|
||||
files (it only reads from streams or `std::FILE*` handles that the caller has already opened), does not read environment
|
||||
variables, and does not implement cryptography or handle credentials.
|
||||
|
||||
The primary threat is therefore **untrusted input**: JSON text or binary data (BJData, BSON, CBOR, MessagePack, UBJSON)
|
||||
that an attacker controls, passed to [`parse`](../api/basic_json/parse.md), [`accept`](../api/basic_json/accept.md),
|
||||
[`sax_parse`](../api/basic_json/sax_parse.md), or one of the `from_*` functions such as
|
||||
[`from_cbor`](../api/basic_json/from_cbor.md). Such input may try to
|
||||
|
||||
- make the library read or write out of bounds (malformed lengths, truncated input, invalid UTF-8),
|
||||
- trigger undefined behavior (integer overflow in sizes or numbers, invalid casts),
|
||||
- exhaust memory (huge announced sizes), or
|
||||
- exhaust the call stack (deeply nested arrays and objects).
|
||||
|
||||
## Trust boundaries
|
||||
|
||||
- **Untrusted:** all serialized input read by the parser, the SAX interface, and the binary readers. The library must
|
||||
handle every possible input by either producing a value or throwing a [`parse_error`](../home/exceptions.md#parse-errors)
|
||||
(or returning `false` when exceptions are disabled for the call).
|
||||
- **Trusted:** the C++ code that calls the library. Calling a function with violated preconditions, for instance
|
||||
accessing an array with [`operator[]`](../api/basic_json/operator%5B%5D.md) out of range, is a programming error and
|
||||
not a security boundary. Such preconditions are checked with [runtime assertions](../features/assertions.md) in debug
|
||||
builds; functions such as [`at`](../api/basic_json/at.md) offer checked access with exceptions.
|
||||
|
||||
## Secure design
|
||||
|
||||
- **Strict parsing.** The parser accepts exactly the JSON grammar of [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
Extensions such as [comments](../features/comments.md) and [trailing commas](../features/trailing_commas.md) must be
|
||||
enabled explicitly. Invalid UTF-8 is rejected.
|
||||
- **Errors are reported, not ignored.** Malformed input results in a [`parse_error`](../home/exceptions.md#parse-errors)
|
||||
with the byte position of the error. Binary readers do not trust announced sizes: strings and binary values grow
|
||||
only as bytes are actually read, arrays reserve at most a fixed number of elements up front, and sizes that no
|
||||
container can hold are rejected.
|
||||
- **Memory is owned by values.** Each `basic_json` value owns its content, and there is no manual memory management in
|
||||
user code. The destructor does not recurse, so destroying a deeply nested value does not exhaust the stack.
|
||||
- **Bounded recursion.** The JSON parser and the binary readers keep their state in explicit stacks instead of
|
||||
recursing per nesting level. Operations that walk a value, such as [`dump`](../api/basic_json/dump.md), copying,
|
||||
comparison, hashing, and [`merge_patch`](../api/basic_json/merge_patch.md), recurse only up to a fixed depth and
|
||||
continue with an explicit stack below it. Some operations, such as [`diff`](../api/basic_json/diff.md),
|
||||
[`flatten`](../api/basic_json/flatten.md), and the binary writers, still recurse once per nesting level; work on them
|
||||
is in progress. Applications that process untrusted input can limit its nesting depth with a
|
||||
[parser callback](../features/parsing/parser_callbacks.md).
|
||||
- **Invariants are checked.** The class invariant (for instance, that the pointer for the stored type is never null) is
|
||||
checked with runtime assertions throughout the test suite.
|
||||
|
||||
## Common weaknesses
|
||||
|
||||
The following table maps the relevant classes of the [Common Weakness Enumeration](https://cwe.mitre.org) to the
|
||||
measures that counter them. The measures are described in detail in [Quality assurance](quality_assurance.md).
|
||||
|
||||
| Weakness | Countermeasures |
|
||||
|---------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
||||
| Out-of-bounds read/write ([CWE-125](https://cwe.mitre.org/data/definitions/125.html), [CWE-787](https://cwe.mitre.org/data/definitions/787.html)) | bounds checks on all reads from the input; AddressSanitizer and Valgrind on the test suite; OSS-Fuzz |
|
||||
| Integer overflow ([CWE-190](https://cwe.mitre.org/data/definitions/190.html)) | UndefinedBehaviorSanitizer with integer overflow detection; Clang-Tidy; Cppcheck |
|
||||
| Use after free, double free ([CWE-416](https://cwe.mitre.org/data/definitions/416.html), [CWE-415](https://cwe.mitre.org/data/definitions/415.html)) | ownership of all memory by values; AddressSanitizer and Valgrind; Clang Static Analyzer |
|
||||
| Memory leaks ([CWE-401](https://cwe.mitre.org/data/definitions/401.html)) | Valgrind (Memcheck) on the test suite |
|
||||
| Uncontrolled recursion ([CWE-674](https://cwe.mitre.org/data/definitions/674.html)) | iterative parser, binary readers, and destructor; bounded recursion in value operations; tests with deeply nested inputs |
|
||||
| Uncontrolled resource consumption ([CWE-400](https://cwe.mitre.org/data/definitions/400.html)) | allocations based on announced sizes are capped; OSS-Fuzz with memory limits |
|
||||
| Undefined behavior in general ([CWE-758](https://cwe.mitre.org/data/definitions/758.html)) | UndefinedBehaviorSanitizer; runtime assertions; Clang-Tidy, Cppcheck, Clang Static Analyzer, Infer |
|
||||
|
||||
In addition, every line of the library is covered by the unit tests, and all parsers are fuzz-tested around the clock
|
||||
by [OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json).
|
||||
@@ -91,6 +91,31 @@ activities include (but are not limited to):
|
||||
Users who continue to engage with the project and its community will often find themselves becoming more and more
|
||||
involved. Such users may then go on to become contributors, as described above.
|
||||
|
||||
## Access to project resources
|
||||
|
||||
The project's resources are the [GitHub repository](https://github.com/nlohmann/json) with its settings, CI workflows
|
||||
and secrets, and the documentation at [json.nlohmann.me](https://json.nlohmann.me), which is built and deployed from
|
||||
the repository. Currently, the project lead is the only person with write or admin access to them.
|
||||
|
||||
### Granting access
|
||||
|
||||
Write or admin access is only granted by the project lead, and only to a contributor whose track record in the project
|
||||
the project lead has reviewed first. The role is assigned manually and is the lowest one that is needed for the task.
|
||||
Access is removed when it is no longer needed. GitHub requires two-factor authentication for everyone who can modify the
|
||||
repository.
|
||||
|
||||
### Secrets
|
||||
|
||||
The CI workflows mostly use the token that GitHub creates for each workflow run. It is read-only by default, and each
|
||||
workflow requests only the additional permissions it needs. The few other credentials, such as the token for
|
||||
[Semgrep](https://semgrep.dev), are stored as encrypted GitHub Actions secrets:
|
||||
|
||||
- Only people with admin access can create, change, or delete them. Their values cannot be read back, not even by
|
||||
admins.
|
||||
- They are not passed to workflows that run for pull requests from forks.
|
||||
- They must never be committed to the repository or printed in logs.
|
||||
- They are rotated whenever someone with admin access leaves the project, and immediately if a leak is suspected.
|
||||
|
||||
## Support
|
||||
|
||||
All participants in the community are encouraged to provide support for new users within the project management
|
||||
|
||||
@@ -5,4 +5,6 @@
|
||||
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
||||
- [Governance](governance.md) - the governance model of this project
|
||||
- [Quality Assurance](quality_assurance.md) - how the quality of this project is assured
|
||||
- [Roadmap](roadmap.md) - what the project will and will not do
|
||||
- [Security Policy](security_policy.md) - the security policy of the project
|
||||
- [Assurance Case](assurance_case.md) - why the library meets its security requirements
|
||||
|
||||
@@ -164,6 +164,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
- [x] The parser is tested against extensive correctness suites for JSON compliance.
|
||||
- [x] In addition, the library is continuously fuzz-tested at [OSS-Fuzz](https://google.github.io/oss-fuzz/) where the
|
||||
library is checked against billions of inputs.
|
||||
- [x] Every crash reported by OSS-Fuzz is fixed together with a unit test that reproduces it, and the fix references
|
||||
the OSS-Fuzz issue. The round-trip checks of the fuzzer drivers are also part of the unit tests. See the
|
||||
[fuzz testing documentation](https://github.com/nlohmann/json/blob/develop/tests/fuzzing.md#handling-oss-fuzz-reports).
|
||||
|
||||
## Static analysis
|
||||
|
||||
@@ -197,6 +200,25 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa
|
||||
- [x] The test suite is executed with [Sanitizers](https://github.com/google/sanitizers) (address sanitizer, undefined
|
||||
behavior sanitizer, integer overflow detection, nullability violations).
|
||||
|
||||
## Dependencies
|
||||
|
||||
!!! success "Requirement: No vulnerable dependencies"
|
||||
|
||||
The library has no dependencies besides the C++ standard library. The tools used to build, test, and document it
|
||||
are kept free of known vulnerabilities.
|
||||
|
||||
- [x] GitHub Actions are pinned to a commit hash, and the Python packages used by the documentation and the tools are
|
||||
pinned to exact versions.
|
||||
- [x] [Dependabot](https://docs.github.com/en/code-security/dependabot) checks these dependencies daily and proposes
|
||||
updates as pull requests.
|
||||
- [x] Every pull request is checked with the
|
||||
[dependency review action](https://github.com/actions/dependency-review-action). A pull request that adds a
|
||||
dependency with a known vulnerability of any severity fails this check and is not merged.
|
||||
- [x] Vulnerability alerts for dependencies are fixed or dismissed with a documented reason before the next release.
|
||||
No release is made while such an alert is open.
|
||||
- [x] Third-party code included in the repository for testing, such as [doctest](https://github.com/doctest/doctest),
|
||||
is updated manually.
|
||||
|
||||
## Style check
|
||||
|
||||
!!! success "Requirement: Common code style"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Roadmap
|
||||
|
||||
This page describes what the project intends to do, and what it does not intend to do, over the next year. Concrete
|
||||
work items are tracked in the [GitHub milestones](https://github.com/nlohmann/json/milestones) and the
|
||||
[issue tracker](https://github.com/nlohmann/json/issues).
|
||||
|
||||
## What the project will do
|
||||
|
||||
- **Keep the C++11 baseline.** The library will continue to compile with every
|
||||
[supported C++11 compiler](https://github.com/nlohmann/json/blob/develop/README.md#supported-compilers). Features of
|
||||
later standards are only used when they are guarded by the `JSON_HAS_CPP_*` macros.
|
||||
- **Stay conformant to JSON.** The parser and serializer follow [RFC 8259](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
Extensions such as [comments](../features/comments.md) or [trailing commas](../features/trailing_commas.md) remain
|
||||
opt-in.
|
||||
- **Keep the 3.x public API stable.** Releases follow [semantic versioning](https://semver.org). Changes that would
|
||||
break existing code are only added behind a feature macro, so users can opt in and test their code before a next
|
||||
major release.
|
||||
- **Support a broad range of compilers and platforms.** The [CI](quality_assurance.md) keeps testing old and new
|
||||
versions of GCC, Clang, MSVC, and other compilers on Linux, macOS, and Windows.
|
||||
- **Keep the quality assurance up.** Every change keeps the test coverage at 100%, passes the static and dynamic
|
||||
analysis, and is fuzz-tested by OSS-Fuzz, see [Quality assurance](quality_assurance.md).
|
||||
- **Harden the library against hostile input.** Handling deeply nested values without exhausting the call stack is
|
||||
ongoing work.
|
||||
- **Fix bugs and security issues** reported through the issue tracker and the [security policy](security_policy.md).
|
||||
|
||||
## What the project will not do
|
||||
|
||||
- **Break the public API of version 3.x.** See the
|
||||
[contribution guidelines](https://github.com/nlohmann/json/blob/develop/.github/CONTRIBUTING.md#break-the-public-api)
|
||||
for what counts as a breaking change.
|
||||
- **Require a newer C++ standard than C++11.**
|
||||
- **Break JSON conformance** or enable non-standard extensions by default.
|
||||
- **Add dependencies** or require a build step. The library remains header-only, and the single header
|
||||
`json.hpp` remains a complete distribution.
|
||||
- **Trade simplicity for speed or memory efficiency.** Performance improvements are welcome, but the library is not
|
||||
meant to compete with the fastest JSON libraries, see [Design goals](../home/design_goals.md).
|
||||
|
||||
## Version 4.0
|
||||
|
||||
There is no decision yet on whether or when a version 4.0 with breaking changes will be released. Proposals that need
|
||||
a major version, for instance stricter type conversions, are collected in issue
|
||||
[#3453](https://github.com/nlohmann/json/issues/3453). Until then, such changes are only added as opt-in behavior
|
||||
behind feature macros.
|
||||
@@ -215,6 +215,24 @@ For _derived_ classes and structs, use the following macros
|
||||
nlohmann::ordered_json j = p; // keys appear in declaration order: name, address, age
|
||||
```
|
||||
|
||||
!!! note "Zero-member types"
|
||||
|
||||
All 12 `NLOHMANN_DEFINE_TYPE_*`/`NLOHMANN_DEFINE_DERIVED_TYPE_*` macros (excluding the `WITH_NAMES` variants)
|
||||
also accept types with no member variables to serialize, producing/accepting an empty JSON object `{}`
|
||||
(or, for the derived-type macros, just the base class's own JSON representation):
|
||||
|
||||
```cpp
|
||||
namespace ns {
|
||||
struct marker {
|
||||
bool operator==(const marker&) const { return true; }
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE(marker)
|
||||
};
|
||||
}
|
||||
|
||||
ns::marker m{};
|
||||
nlohmann::json j = m; // {}
|
||||
```
|
||||
|
||||
!!! note "No macro for non-default-constructible types"
|
||||
|
||||
There is currently no `NLOHMANN_DEFINE_TYPE_*`-style macro for types that are not
|
||||
|
||||
@@ -116,18 +116,22 @@ The library uses the following mapping from JSON values types to BJData types ac
|
||||
```
|
||||
|
||||
Likewise, when a JSON object in the above form is serialized using
|
||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array. When
|
||||
the 1-dimensional vector stored in `"_ArraySize_"` contains a single integer or two integers with one being 1, a
|
||||
regular 1-D optimized array is generated instead.
|
||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md), it is automatically converted into a compact BJData ND-array.
|
||||
|
||||
An object is only converted if the annotation actually describes a packed array; otherwise it is serialized as a
|
||||
regular JSON object. This requires all of the following:
|
||||
When parsing, an ND-array whose dimension vector is empty, contains a single integer, contains two integers with the
|
||||
first being 1, or contains a 0 is returned as a regular (possibly empty) array rather than an annotated object.
|
||||
|
||||
An object is only converted if the annotation describes a packed array that is parsed back into the same annotated
|
||||
object; otherwise it is serialized as a regular JSON object, so the annotation is never lost in a round trip. This requires
|
||||
all of the following:
|
||||
|
||||
- `"_ArrayType_"` is one of `uint8`, `int8`, `uint16`, `int16`, `uint32`, `int32`, `uint64`, `int64`, `single`,
|
||||
`double`, `char`, or `byte`,
|
||||
- `"_ArraySize_"` is an array, since the dimensions are written as the ND-array header's length,
|
||||
- every entry of `"_ArraySize_"` is a non-negative integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` holds exactly that many elements, and
|
||||
- `"_ArraySize_"` has at least two entries and is not a 1×N row vector (first entry 1), since other shapes are
|
||||
parsed back as a regular array,
|
||||
- every entry of `"_ArraySize_"` is a positive integer, and their product is representable as a `std::size_t`,
|
||||
- `"_ArrayData_"` is an array holding exactly that many elements, and
|
||||
- every element of `"_ArrayData_"` is a number of the kind named by `"_ArrayType_"` (a floating-point number for
|
||||
`single` and `double`, an integer otherwise).
|
||||
|
||||
@@ -204,6 +208,16 @@ The library maps BJData types to JSON value types as follows:
|
||||
|
||||
The mapping is **complete** in the sense that any BJData value can be converted to a JSON value.
|
||||
|
||||
!!! info "Round trips"
|
||||
|
||||
A value returned by [`from_bjdata`](../../api/basic_json/from_bjdata.md) can be serialized with
|
||||
[`to_bjdata`](../../api/basic_json/to_bjdata.md) using any combination of options and parsed back into an equal
|
||||
value, and serializing that value again with the same options produces the same bytes. The exception is binary
|
||||
values: they are only written as an optimized binary array (`[$B`) if Draft 3 is enabled and both `use_size` and
|
||||
`use_type` are set. Otherwise, they are written as arrays of integers and parsed back as such (see the notes on
|
||||
binary values above), and serializing such an array again may choose different, but equally valid, type markers.
|
||||
The bytes can then differ, but parsing them again yields the same value.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -109,6 +109,15 @@ The library maps BSON record types to JSON value types as follows:
|
||||
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
||||
`from_bson()`.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The BSON specification requires `string` values (type `0x02`) to be valid UTF-8. This library validates the
|
||||
bytes of every such string at decode time and rejects ill-formed UTF-8 with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with `allow_exceptions`
|
||||
set to `false`, a discarded value), rather than only failing later when the resulting value is dumped. Element
|
||||
(key) names and `binary` values (type `0x05`) are unaffected and are never validated, since they are read
|
||||
byte-by-byte as a C string, or are not required to hold text, respectively.
|
||||
|
||||
??? example
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -176,6 +176,16 @@ The library maps CBOR types to JSON value types as follows:
|
||||
|
||||
CBOR allows map keys of any type, whereas JSON only allows strings as keys in object values. Therefore, CBOR maps with keys other than UTF-8 strings are rejected.
|
||||
|
||||
!!! warning "UTF-8 validation of text strings"
|
||||
|
||||
[RFC 8949, Section 3.1](https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1) requires CBOR text strings
|
||||
(major type 3) to be valid UTF-8. This library validates the bytes of every text string (object keys included) at
|
||||
decode time and rejects ill-formed UTF-8 with a
|
||||
[`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or, with
|
||||
`allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting value is
|
||||
dumped. Byte strings (major type 2) are unaffected and are never validated, since they are not required to hold
|
||||
text.
|
||||
|
||||
!!! warning "Tagged items"
|
||||
|
||||
Tagged items (0xC0..0xDB) will throw a parse error by default. They can be ignored by passing `cbor_tag_handler_t::ignore` to function `from_cbor`, in which case the tag is skipped and the enclosed data item is parsed on its own. They can be stored by passing `cbor_tag_handler_t::store` to function `from_cbor`. Note that no tag is ever interpreted: for instance, a text string tagged with tag 0 (date/time) stays a string.
|
||||
|
||||
@@ -136,6 +136,14 @@ The library maps MessagePack types to JSON value types as follows:
|
||||
|
||||
Any MessagePack output created by `to_msgpack` can be successfully parsed by `from_msgpack`.
|
||||
|
||||
!!! warning "UTF-8 validation of string values"
|
||||
|
||||
The MessagePack specification requires `str` values (`fixstr`, `str 8`, `str 16`, `str 32`) to be valid UTF-8.
|
||||
This library validates the bytes of every such string (object keys included) at decode time and rejects
|
||||
ill-formed UTF-8 with a [`parse_error.113`](../../home/exceptions.md#jsonexceptionparse_error113) exception (or,
|
||||
with `allow_exceptions` set to `false`, a discarded value), rather than only failing later when the resulting
|
||||
value is dumped. `bin`/`ext`/`fixext` values are unaffected and are never validated, since they are not required
|
||||
to hold text.
|
||||
|
||||
??? example
|
||||
|
||||
|
||||
@@ -91,6 +91,23 @@ security reasons (e.g., Intel Software Guard Extensions (SGX)).
|
||||
|
||||
See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
||||
|
||||
## `JSON_NO_THREAD_LOCAL`
|
||||
|
||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||
always avoid the call stack rather than descending into a bounded number of levels first, which is slower but yields the
|
||||
same values and the same comparisons.
|
||||
|
||||
See [full documentation of `JSON_NO_THREAD_LOCAL`](../api/macros/json_no_thread_local.md).
|
||||
|
||||
## `JSON_PRECISE_STREAM_POSITION`
|
||||
|
||||
When defined to `1`, [`operator>>`](../api/operator_gtgt.md) and non-strict
|
||||
[`sax_parse`](../api/basic_json/sax_parse.md) leave an input stream positioned right after the parsed value, instead of
|
||||
also consuming the character that terminates a number. The default value is `0`, which preserves the existing behavior;
|
||||
this is planned to become the default in version 4.0.0.
|
||||
|
||||
See [full documentation of `JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md).
|
||||
|
||||
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
|
||||
|
||||
When defined, the library will not create a compiler warning when a different version of the library was already
|
||||
@@ -105,6 +122,19 @@ using the library with compilers that do not fully support C++11 and may only wo
|
||||
|
||||
See [full documentation of `JSON_SKIP_UNSUPPORTED_COMPILER_CHECK`](../api/macros/json_skip_unsupported_compiler_check.md).
|
||||
|
||||
## `JSON_STRICT_NUL_HANDLING`
|
||||
|
||||
When defined to `1`, a `'\0'` (NUL) byte anywhere in the input is rejected with `parse_error.101`, like any other
|
||||
unexpected byte, instead of being silently treated as end of input (see the
|
||||
[FAQ entry](../home/faq.md#nul-bytes-in-the-input) for background). The default value is `0`, which preserves the
|
||||
existing behavior; this is planned to become the default in version 4.0.0.
|
||||
|
||||
The strict handling can also be enabled with the CMake option
|
||||
[`JSON_StrictNulHandling`](../integration/cmake.md#json_strictnulhandling) (`OFF` by default) which sets
|
||||
`JSON_STRICT_NUL_HANDLING` accordingly.
|
||||
|
||||
See [full documentation of `JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md).
|
||||
|
||||
## `JSON_THROW_USER(exception)`
|
||||
|
||||
This macro overrides `#!cpp throw` calls inside the library. The argument is the exception to be thrown.
|
||||
|
||||
@@ -15,6 +15,11 @@ The complete default namespace name is derived as follows:
|
||||
- [`JSON_DIAGNOSTICS`](../api/macros/json_diagnostics.md) defined non-zero appends `_diag`.
|
||||
- [`JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`](../api/macros/json_use_legacy_discarded_value_comparison.md)
|
||||
defined non-zero appends `_ldvcmp`.
|
||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) defined non-zero appends `_dp`.
|
||||
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) defined non-zero appends
|
||||
`_bics`.
|
||||
- [`JSON_PRECISE_STREAM_POSITION`](../api/macros/json_precise_stream_position.md) defined non-zero appends `_psp`.
|
||||
- [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) defined non-zero appends `_snul`.
|
||||
- The inline namespace ends with the suffix `_v` followed by the 3 components of the version number separated by
|
||||
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||
below.
|
||||
|
||||
@@ -41,7 +41,8 @@ document followed by trailing bytes" is accepted rather than rejected. If you ar
|
||||
reject any input that is not exactly one JSON document, prefer `parse`.
|
||||
|
||||
When using `operator>>` to read several concatenated values this way, a value that is a number must be followed by
|
||||
whitespace, because `operator>>` consumes the character that terminates a number — see the
|
||||
whitespace, because `operator>>` consumes the character that terminates a number, unless
|
||||
[`JSON_PRECISE_STREAM_POSITION`](../../api/macros/json_precise_stream_position.md) is defined to `1` — see the
|
||||
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||
|
||||
## SAX vs. DOM parsing
|
||||
|
||||
@@ -562,7 +562,11 @@ binary32 or binary64 field and have no encoding for `#!cpp long double`.
|
||||
## `AllocatorType`
|
||||
|
||||
`AllocatorType` is instantiated with **one** argument, for each of `object_t`, `array_t`, `string_t`, `binary_t`,
|
||||
`basic_json`, and `#!cpp std::pair<const StringType, basic_json>`.
|
||||
`basic_json`, `#!cpp std::pair<const StringType, basic_json>`, and `#!cpp std::pair<StringType, basic_json>`.
|
||||
|
||||
`AllocatorType` is not the only allocator a `basic_json` uses. It allocates the JSON values themselves, but most
|
||||
temporary storage is allocated with `#!cpp std::allocator`. This includes the parser's stacks and the stacks that
|
||||
process deeply nested values without recursion.
|
||||
|
||||
### Always required
|
||||
|
||||
|
||||
@@ -1,34 +1,125 @@
|
||||
# Architecture
|
||||
|
||||
!!! info
|
||||
|
||||
This page is still under construction. Its goal is to provide a high-level overview of the library's architecture.
|
||||
This should help new contributors to get an idea of the used concepts and where to make changes.
|
||||
This page gives a high-level overview of the library's architecture. It should help new contributors to get an idea of
|
||||
the used concepts and where to make changes.
|
||||
|
||||
## Overview
|
||||
|
||||
The main structure is class [nlohmann::basic_json](../api/basic_json/index.md).
|
||||
The library is built around a single class template, [`nlohmann::basic_json`](../api/basic_json/index.md). A
|
||||
`basic_json` value is a node in a tree of JSON values. All other components either create such a tree from an input
|
||||
(parsing), write a tree to an output (serialization), or give access to it (iterators, JSON Pointer, conversions).
|
||||
|
||||
- public API
|
||||
- container interface
|
||||
- iterators
|
||||
```mermaid
|
||||
flowchart LR
|
||||
input[/"input<br>(string, stream,<br>iterator range, file)"/]
|
||||
ia["input adapter"]
|
||||
lexer["lexer"]
|
||||
parser["parser"]
|
||||
breader["binary_reader"]
|
||||
sax["SAX interface"]
|
||||
value[("basic_json<br>value tree")]
|
||||
serializer["serializer"]
|
||||
bwriter["binary_writer"]
|
||||
oa["output adapter"]
|
||||
output[/"output<br>(string, stream,<br>vector)"/]
|
||||
|
||||
## Template specializations
|
||||
input --> ia
|
||||
ia --> lexer --> parser --> sax
|
||||
ia --> breader --> sax
|
||||
sax --> value
|
||||
value --> serializer --> oa
|
||||
value --> bwriter --> oa
|
||||
oa --> output
|
||||
```
|
||||
|
||||
- describe template parameters of `basic_json`
|
||||
- [`json`](../api/json.md)
|
||||
- [`ordered_json`](../api/ordered_json.md) via [`ordered_map`](../api/ordered_map.md)
|
||||
- **JSON text** is read by an [input adapter](#input-adapters), tokenized by the lexer, and turned into SAX events by
|
||||
the parser.
|
||||
- **Binary formats** (BJData, BSON, CBOR, MessagePack, UBJSON) are read by an input adapter and turned into the same SAX
|
||||
events by the `binary_reader`.
|
||||
- A [SAX consumer](#sax-interface) receives the events. The one used by [`parse`](../api/basic_json/parse.md) builds a
|
||||
`basic_json` value tree.
|
||||
- The `serializer` (JSON text) or the `binary_writer` (binary formats) writes a value tree to an
|
||||
[output adapter](#output-adapters).
|
||||
|
||||
## Source layout
|
||||
|
||||
The public headers are in [`include/nlohmann`](https://github.com/nlohmann/json/tree/develop/include/nlohmann):
|
||||
|
||||
- [`json.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json.hpp) defines class [`basic_json`](../api/basic_json/index.md).
|
||||
- [`json_fwd.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/json_fwd.hpp) contains forward declarations.
|
||||
- [`adl_serializer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/adl_serializer.hpp), [`byte_container_with_subtype.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/byte_container_with_subtype.hpp), and [`ordered_map.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/ordered_map.hpp) define
|
||||
[`adl_serializer`](../api/adl_serializer/index.md),
|
||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md), and
|
||||
[`ordered_map`](../api/ordered_map.md).
|
||||
|
||||
Everything else lives in [`detail/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail) and namespace `nlohmann::detail`, which is not part of the public API. Paths
|
||||
below are relative to `include/nlohmann`.
|
||||
|
||||
| Component | Location |
|
||||
|-----------|----------|
|
||||
| Value type enumeration | [`detail/value_t.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/value_t.hpp) |
|
||||
| Input adapters | [`detail/input/input_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/input_adapters.hpp) |
|
||||
| Lexer | [`detail/input/lexer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/lexer.hpp), [`detail/input/number_parse.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/number_parse.hpp), [`detail/input/string_scan.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/string_scan.hpp) |
|
||||
| Parser | [`detail/input/parser.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/parser.hpp) |
|
||||
| SAX interface and DOM builders | [`detail/input/json_sax.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/json_sax.hpp) |
|
||||
| Binary format readers | [`detail/input/binary_reader.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/input/binary_reader.hpp) |
|
||||
| JSON serializer | [`detail/output/serializer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/output/serializer.hpp), [`detail/conversions/to_chars.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/conversions/to_chars.hpp) |
|
||||
| Binary format writers | [`detail/output/binary_writer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/output/binary_writer.hpp) |
|
||||
| Output adapters | [`detail/output/output_adapters.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/output/output_adapters.hpp) |
|
||||
| Iterators | [`detail/iterators/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail/iterators) |
|
||||
| Conversions from/to arbitrary types | [`detail/conversions/from_json.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/conversions/from_json.hpp), [`detail/conversions/to_json.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/conversions/to_json.hpp) |
|
||||
| JSON Pointer | [`detail/json_pointer.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/json_pointer.hpp) |
|
||||
| Exceptions | [`detail/exceptions.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/exceptions.hpp) |
|
||||
| Type traits and C++ feature backports | [`detail/meta/`](https://github.com/nlohmann/json/tree/develop/include/nlohmann/detail/meta) |
|
||||
| Macros | [`detail/macro_scope.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/macro_scope.hpp), [`detail/macro_unscope.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/macro_unscope.hpp), [`detail/abi_macros.hpp`](https://github.com/nlohmann/json/blob/develop/include/nlohmann/detail/abi_macros.hpp) |
|
||||
|
||||
The single-header version [`single_include/nlohmann/json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp)
|
||||
is generated from these files with `make amalgamate` and must not be edited by hand.
|
||||
|
||||
## Template parameters
|
||||
|
||||
[`basic_json`](../api/basic_json/index.md) is parameterized by the types it uses to store values and to convert from and to other types:
|
||||
|
||||
| Template parameter | Default | Used for |
|
||||
|----------------------|-----------------------------|-------------------------------------------------------------------|
|
||||
| `ObjectType` | `std::map` | objects, see [`object_t`](../api/basic_json/object_t.md) |
|
||||
| `ArrayType` | `std::vector` | arrays, see [`array_t`](../api/basic_json/array_t.md) |
|
||||
| `StringType` | `std::string` | strings and object keys, see [`string_t`](../api/basic_json/string_t.md) |
|
||||
| `BooleanType` | `bool` | Booleans, see [`boolean_t`](../api/basic_json/boolean_t.md) |
|
||||
| `NumberIntegerType` | `std::int64_t` | signed integers, see [`number_integer_t`](../api/basic_json/number_integer_t.md) |
|
||||
| `NumberUnsignedType` | `std::uint64_t` | unsigned integers, see [`number_unsigned_t`](../api/basic_json/number_unsigned_t.md) |
|
||||
| `NumberFloatType` | `double` | floating-point numbers, see [`number_float_t`](../api/basic_json/number_float_t.md) |
|
||||
| `AllocatorType` | `std::allocator` | allocating objects, arrays, strings, and binary values |
|
||||
| `JSONSerializer` | `adl_serializer` | conversions from/to other types, see [`adl_serializer`](../api/adl_serializer/index.md) |
|
||||
| `BinaryType` | `std::vector<std::uint8_t>` | binary values, see [`binary_t`](../api/basic_json/binary_t.md) |
|
||||
| `CustomBaseClass` | `void` | an optional base class, see [`json_base_class_t`](../api/basic_json/json_base_class_t.md) |
|
||||
|
||||
The library provides two specializations:
|
||||
|
||||
- [`json`](../api/json.md) uses all default template arguments.
|
||||
- [`ordered_json`](../api/ordered_json.md) uses [`ordered_map`](../api/ordered_map.md) as `ObjectType` to keep the
|
||||
insertion order of object keys.
|
||||
|
||||
The requirements on the template arguments are listed in
|
||||
[Template Parameter Requirements](../features/types/template_parameters.md).
|
||||
|
||||
## Value storage
|
||||
|
||||
Values are stored as a tagged union of [value_t](../api/basic_json/value_t.md) and json_value.
|
||||
Each [`basic_json`](../api/basic_json/index.md) value stores its content as a tagged union: an enumeration [`value_t`](../api/basic_json/value_t.md)
|
||||
names the type of the value, and a union `json_value` holds the value itself. Both are members of the nested struct
|
||||
`data`, which is the only data member `m_data` of `basic_json`:
|
||||
|
||||
```cpp
|
||||
/// the type of the current element
|
||||
value_t m_type = value_t::null;
|
||||
struct data
|
||||
{
|
||||
/// the type of the current element
|
||||
value_t m_type = value_t::null;
|
||||
|
||||
/// the value of the current element
|
||||
json_value m_value = {};
|
||||
/// the value of the current element
|
||||
json_value m_value = {};
|
||||
};
|
||||
|
||||
data m_data = {};
|
||||
```
|
||||
|
||||
with
|
||||
@@ -68,42 +159,83 @@ union json_value {
|
||||
};
|
||||
```
|
||||
|
||||
## Parsing inputs (deserialization)
|
||||
Objects, arrays, strings, and binary values are allocated on the heap with `AllocatorType`, and the union only stores a
|
||||
pointer to them. This keeps a `basic_json` value small: one pointer-sized union and one byte for the type. The class
|
||||
maintains the invariant that the pointer matching `m_type` is never null; `assert_invariant()` checks it with
|
||||
[runtime assertions](../features/assertions.md).
|
||||
|
||||
Input is read via **input adapters** that abstract a source with a common interface:
|
||||
## Input adapters
|
||||
|
||||
Input is read via **input adapters** that abstract a source. Every input adapter provides this interface:
|
||||
|
||||
```cpp
|
||||
/// read a single character
|
||||
std::char_traits<char>::int_type get_character() noexcept;
|
||||
/// the type of the characters in the input
|
||||
using char_type = ...;
|
||||
|
||||
/// read multiple characters to a destination buffer and
|
||||
/// returns the number of characters successfully read
|
||||
/// read a single character; returns std::char_traits<char_type>::eof() at the end of the input
|
||||
typename std::char_traits<char_type>::int_type get_character();
|
||||
|
||||
/// read up to count * sizeof(T) bytes into dest and return the number of bytes read
|
||||
/// (used by the binary readers)
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1);
|
||||
```
|
||||
|
||||
List examples of input adapters.
|
||||
The lexer detects two optional extensions at compile time. Only `iterator_input_adapter` provides them, and only for
|
||||
random-access input of single-byte characters:
|
||||
|
||||
## SAX Interface
|
||||
- `supports_seek`, `get_consumed_count()`, and `copy_consumed_range()` let the lexer reconstruct already consumed input
|
||||
for error messages instead of copying every character it reads.
|
||||
- `supports_bulk_scan`, `bulk_data()`, `bulk_remaining()`, and `bulk_skip()` let the lexer scan strings directly in
|
||||
contiguous memory, several bytes at a time.
|
||||
|
||||
TODO
|
||||
The function `input_adapter` picks the right adapter for the argument passed to `parse`, `accept`, `sax_parse`, or the
|
||||
`from_*` functions:
|
||||
|
||||
## Writing outputs (serialization)
|
||||
- `iterator_input_adapter` reads from an iterator range, which also covers strings, containers, and pointers.
|
||||
- `wide_string_input_adapter` reads from ranges of `wchar_t`, `char16_t`, or `char32_t` and converts them to UTF-8.
|
||||
It cannot be used for binary formats; its `get_elements()` throws.
|
||||
- `input_stream_adapter` reads from a `std::istream`.
|
||||
- `file_input_adapter` reads from a `std::FILE*`.
|
||||
|
||||
## SAX interface
|
||||
|
||||
The parser does not build values itself. It reports what it reads as events to a [SAX](../features/parsing/sax_interface.md)
|
||||
consumer, which implements the interface [`json_sax`](../api/json_sax/index.md): `null`, `boolean`, `number_integer`,
|
||||
`number_unsigned`, `number_float`, `string`, `binary`, `start_object`, `key`, `end_object`, `start_array`, `end_array`,
|
||||
and `parse_error`.
|
||||
|
||||
The library comes with two consumers in `detail/input/json_sax.hpp`:
|
||||
|
||||
- `json_sax_dom_parser` builds a [`basic_json`](../api/basic_json/index.md) value tree. [`parse`](../api/basic_json/parse.md) uses it.
|
||||
- `json_sax_dom_callback_parser` does the same, but calls a [parser callback](../features/parsing/parser_callbacks.md)
|
||||
for each event, which can skip values. `parse` uses it when a callback is given.
|
||||
|
||||
The `binary_reader` emits the same events for binary formats, so [`sax_parse`](../api/basic_json/sax_parse.md) works
|
||||
with a user-defined consumer for JSON and for all binary formats alike.
|
||||
|
||||
## Output adapters
|
||||
|
||||
Output is written via **output adapters**:
|
||||
|
||||
```cpp
|
||||
template<typename T>
|
||||
void write_character(CharType c);
|
||||
|
||||
template<typename CharType>
|
||||
void write_characters(const CharType* s, std::size_t length);
|
||||
```
|
||||
|
||||
List examples of output adapters.
|
||||
The `serializer` (used by [`dump`](../api/basic_json/dump.md) and [`operator<<`](../api/operator_ltlt.md)) and the
|
||||
`binary_writer` (used by the `to_*` functions) write to one of these adapters:
|
||||
|
||||
- `output_vector_adapter` appends to a `std::vector`.
|
||||
- `output_stream_adapter` writes to a `std::ostream`.
|
||||
- `output_string_adapter` appends to a string.
|
||||
|
||||
## Value conversion
|
||||
|
||||
Values are converted from and to other types with the `JSONSerializer` template parameter. The default,
|
||||
[`adl_serializer`](../api/adl_serializer/index.md), calls the free functions
|
||||
|
||||
```cpp
|
||||
template<class T>
|
||||
void to_json(basic_json& j, const T& t);
|
||||
@@ -112,13 +244,23 @@ template<class T>
|
||||
void from_json(const basic_json& j, T& t);
|
||||
```
|
||||
|
||||
found by argument-dependent lookup. The library defines them for standard types in `detail/conversions`; users add them
|
||||
for their own types, see [Arbitrary Type Conversions](../features/arbitrary_types.md). The
|
||||
[serialization macros](../features/macros.md) generate these functions.
|
||||
|
||||
## Additional features
|
||||
|
||||
- JSON Pointers
|
||||
- Binary formats
|
||||
- Custom base class
|
||||
- Conversion macros
|
||||
- [JSON Pointer](../features/json_pointer.md) (class `json_pointer`) addresses values inside a tree. It is also the
|
||||
basis of [JSON Patch](../features/json_patch.md).
|
||||
- [Binary formats](../features/binary_formats/index.md) are read by `binary_reader` and written by `binary_writer`.
|
||||
- A [custom base class](../api/basic_json/json_base_class_t.md) can add members to every [`basic_json`](../api/basic_json/index.md) value.
|
||||
- [Serialization macros](../features/macros.md) generate `to_json` and `from_json` functions for user-defined types.
|
||||
|
||||
## Details namespace
|
||||
|
||||
- C++ feature backports
|
||||
Namespace `nlohmann::detail` contains all implementation details. It is not part of the public API and may change in any
|
||||
release. Besides the components above, it contains:
|
||||
|
||||
- type traits to detect the capabilities of user-defined types (`detail/meta/type_traits.hpp`),
|
||||
- backports of C++14/17 features to C++11 (`detail/meta/cpp_future.hpp`), and
|
||||
- helpers such as `string_concat` and `string_escape`.
|
||||
|
||||
@@ -6,7 +6,7 @@ There are myriads of [JSON](https://json.org) libraries out there, and each may
|
||||
|
||||
- **Trivial integration**. Our whole code consists of a single header file [`json.hpp`](https://github.com/nlohmann/json/blob/develop/single_include/nlohmann/json.hpp). That's it. No library, no subproject, no dependencies, no complex build system. The class is written in vanilla C++11. All in all, everything should require no adjustment of your compiler flags or project settings.
|
||||
|
||||
- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [Core Infrastructure Initiative (CII) best practices](https://bestpractices.coreinfrastructure.org/projects/289).
|
||||
- **Serious testing**. Our class is heavily [unit-tested](https://github.com/nlohmann/json/tree/develop/tests/src) and covers [100%](https://coveralls.io/r/nlohmann/json) of the code, including all exceptional behavior. Furthermore, we checked with [Valgrind](http://valgrind.org) and the [Clang Sanitizers](https://clang.llvm.org/docs/index.html) that there are no memory leaks. [Google OSS-Fuzz](https://github.com/google/oss-fuzz/tree/master/projects/json) additionally runs fuzz tests against all parsers 24/7, effectively executing billions of tests so far. To maintain high quality, the project is following the [OpenSSF Best Practices](https://www.bestpractices.dev/projects/289).
|
||||
|
||||
Other aspects were not so important to us:
|
||||
|
||||
|
||||
@@ -340,7 +340,8 @@ An unexpected byte was read in a [binary format](../features/binary_formats/inde
|
||||
### json.exception.parse_error.113
|
||||
|
||||
A string could not be read from a [binary format](../features/binary_formats/index.md): either a value that is not a
|
||||
string was read where one was required (for instance as a map key), or the string's length specification is invalid.
|
||||
string was read where one was required (for instance as a map key), the string's length specification is invalid, or
|
||||
the string's bytes are not valid UTF-8.
|
||||
|
||||
!!! failure "Example messages"
|
||||
|
||||
@@ -356,6 +357,9 @@ string was read where one was required (for instance as a map key), or the strin
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative
|
||||
```
|
||||
```
|
||||
[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte
|
||||
```
|
||||
|
||||
### json.exception.parse_error.114
|
||||
|
||||
@@ -970,6 +974,21 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa
|
||||
|
||||
This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.
|
||||
|
||||
### json.exception.out_of_range.415
|
||||
|
||||
MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
|
||||
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.
|
||||
|
||||
!!! failure "Example message"
|
||||
|
||||
```
|
||||
[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error.
|
||||
|
||||
## Further exceptions
|
||||
|
||||
This exception is thrown in case of errors that cannot be classified with the
|
||||
|
||||
@@ -90,6 +90,54 @@ The library supports **Unicode input** as follows:
|
||||
In most cases, the parser is right to complain, because the input is not UTF-8 encoded. This is especially true for Microsoft Windows, where Latin-1 or ISO 8859-1 is often the standard encoding.
|
||||
|
||||
|
||||
### NUL bytes in the input
|
||||
|
||||
!!! question "Questions"
|
||||
|
||||
- Why does `json::parse()` silently ignore part of my input?
|
||||
- Why does a `std::string`/buffer with extra data after the JSON text parse without error, while a similar-looking string with extra text does not?
|
||||
|
||||
A `'\0'` (NUL) byte anywhere in the input is treated the same as the real end of the input, rather than as an ordinary (and, outside of a string, invalid) byte. Everything from that byte onward is silently ignored, without a parse error — including further, otherwise well-formed JSON:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("123") + '\0'); // == 123, no error
|
||||
json::parse(std::string("123") + '\0' + "true"); // == 123, the "true" is silently ignored too
|
||||
```
|
||||
|
||||
This is different from any other unexpected trailing byte, which *does* raise [`parse_error.101`](../home/exceptions.md#jsonexceptionparse_error101):
|
||||
|
||||
```cpp
|
||||
json::parse("123x"); // throws parse_error.101: unexpected additional data
|
||||
```
|
||||
|
||||
This falls out of the same convention used when no explicit input length is given at all: `json::parse(const char*)` already stops at the first NUL byte via `strlen()`, since a bare pointer has no length of its own. The library applies that same NUL-terminated-C-string convention uniformly, rather than only when a length is genuinely unavailable — so a `std::string`, iterator range, or container whose content happens to include a NUL byte is affected the same way a raw `const char*` would be.
|
||||
|
||||
If your input may contain a trailing or embedded NUL that is **not** meant to signal the end of the JSON text — for instance, a fixed-size, zero-padded buffer — trim it yourself before calling `parse()`, since the library will otherwise silently stop there instead of raising an error:
|
||||
|
||||
```cpp
|
||||
s.resize(s.find('\0')); // drop everything from the first NUL onward, if any
|
||||
json::parse(s);
|
||||
```
|
||||
|
||||
**Opt-in strict handling (since version 3.13.0)**
|
||||
|
||||
Manually trimming every input is easy to forget. If you define [`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md) to `1` before including the library, a `'\0'` byte is instead rejected like any other unexpected byte and raises `parse_error.101`, instead of being treated as end of input:
|
||||
|
||||
```cpp
|
||||
#define JSON_STRICT_NUL_HANDLING 1
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
json::parse(std::string("123") + '\0'); // throws parse_error.101 instead of silently returning 123
|
||||
```
|
||||
|
||||
This macro defaults to `0` (disabled, preserving the behavior described above) to avoid breaking existing code that may depend on it, even unknowingly; it is planned to become the default in version 4.0.0. See [its documentation](../api/macros/json_strict_nul_handling.md) for details, including how it also affects `char` arrays such as string literals.
|
||||
|
||||
Note that this is unrelated to an *unescaped* NUL byte occurring **inside** a quoted JSON string, which is a different, already-invalid case and is correctly rejected either way:
|
||||
|
||||
```cpp
|
||||
json::parse(std::string("\"") + '\0' + "\""); // throws parse_error.101: control character U+0000 (NUL) must be escaped to \u0000
|
||||
```
|
||||
|
||||
### Wide string handling
|
||||
|
||||
!!! question
|
||||
|
||||
@@ -198,6 +198,11 @@ Use the non-amalgamated version of the library. This option is `ON` by default.
|
||||
|
||||
Treat the library headers like system headers (i.e., adding `SYSTEM` to the [`target_include_directories`](https://cmake.org/cmake/help/latest/command/target_include_directories.html) call) to check for this library by tools like Clang-Tidy. This option is `OFF` by default.
|
||||
|
||||
### `JSON_StrictNulHandling`
|
||||
|
||||
Reject a `'\0'` (NUL) byte in the input instead of treating it as end of input, by defining the macro
|
||||
[`JSON_STRICT_NUL_HANDLING`](../api/macros/json_strict_nul_handling.md). This option is `OFF` by default.
|
||||
|
||||
### `JSON_Valgrind`
|
||||
|
||||
Execute the test suite with [Valgrind](https://valgrind.org). This option is `OFF` by default. Depends on `JSON_BuildTests`.
|
||||
|
||||
@@ -176,6 +176,12 @@ You can prepare existing code by already defining
|
||||
conversions with calls to [`get`](../api/basic_json/get.md), [`get_to`](../api/basic_json/get_to.md),
|
||||
[`get_ref`](../api/basic_json/get_ref.md), or [`get_ptr`](../api/basic_json/get_ptr.md).
|
||||
|
||||
!!! tip "Automatic migration"
|
||||
|
||||
The community-maintained clang-tidy check `modernize-nlohmann-json-explicit-conversions` rewrites most implicit
|
||||
conversions into calls to [`get`](../api/basic_json/get.md). It is not part of clang-tidy itself; see
|
||||
[discussion #4610](https://github.com/nlohmann/json/discussions/4610) for how to build and use it.
|
||||
|
||||
=== "Deprecated"
|
||||
|
||||
```cpp
|
||||
|
||||
@@ -292,8 +292,11 @@ nav:
|
||||
- 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md
|
||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.md
|
||||
- 'JSON_PRECISE_STREAM_POSITION': api/macros/json_precise_stream_position.md
|
||||
- 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md
|
||||
- 'JSON_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
|
||||
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
|
||||
- 'JSON_USE_GLOBAL_UDLS': api/macros/json_use_global_udls.md
|
||||
- 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md
|
||||
- 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md
|
||||
@@ -315,7 +318,9 @@ nav:
|
||||
- community/contribution_guidelines.md
|
||||
- community/quality_assurance.md
|
||||
- community/governance.md
|
||||
- community/roadmap.md
|
||||
- community/security_policy.md
|
||||
- community/assurance_case.md
|
||||
|
||||
# Extras
|
||||
extra:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
wheel==0.48.0
|
||||
|
||||
mkdocs==1.6.1 # documentation framework
|
||||
mkdocs-git-revision-date-localized-plugin==1.5.4 # plugin "git-revision-date-localized"
|
||||
mkdocs-git-revision-date-localized-plugin==1.6.0 # plugin "git-revision-date-localized"
|
||||
mkdocs-material==9.7.7 # theme for mkdocs
|
||||
mkdocs-material-extensions==1.3.1 # extensions
|
||||
mkdocs-minify-plugin==0.8.0 # plugin "minify"
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_PRECISE_STREAM_POSITION
|
||||
#define JSON_PRECISE_STREAM_POSITION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_STRICT_NUL_HANDLING
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -52,20 +64,41 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION _psp
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING _snul
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -471,6 +471,30 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<
|
||||
j = { std::get<Idx>(t)... };
|
||||
}
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its
|
||||
// element instead of wrapping it, which would serialize std::tuple<int>{5} as 5
|
||||
// rather than [5]. Build what the default deduction builds instead: an object
|
||||
// if the element is a [string, value] pair, a one-element array otherwise.
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/)
|
||||
{
|
||||
BasicJsonType element(std::get<0>(t));
|
||||
// same test as the initializer-list constructor, including the cast that
|
||||
// keeps a string type constructible from 0 from selecting operator[](key)
|
||||
const bool is_member = element.is_array() && element.size() == 2
|
||||
&& element[static_cast<typename BasicJsonType::size_type>(0)].is_string();
|
||||
if (is_member)
|
||||
{
|
||||
j = BasicJsonType::object({std::move(element)});
|
||||
}
|
||||
else
|
||||
{
|
||||
j = BasicJsonType::array({std::move(element)});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename BasicJsonType, typename Tuple>
|
||||
inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)
|
||||
{
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstddef> // size_t
|
||||
#include <functional> // hash
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -26,6 +28,9 @@ inline std::size_t combine(std::size_t seed, std::size_t h) noexcept
|
||||
return seed;
|
||||
}
|
||||
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j);
|
||||
|
||||
/*!
|
||||
@brief hash a JSON value
|
||||
|
||||
@@ -33,12 +38,21 @@ The hash function tries to rely on std::hash where possible. Furthermore, the
|
||||
type of the JSON value is taken into account to have different hash values for
|
||||
null, 0, 0U, and false, etc.
|
||||
|
||||
Hashing an array or an object hashes its elements, which used to call this
|
||||
function again once per nesting level, so a value nested deeply enough
|
||||
exhausted the call stack and terminated the process. The descent is bounded
|
||||
here: once @ref recursion_depth_limit levels have been entered, @ref
|
||||
hash_iteratively hashes what is left without the call stack. A value nested
|
||||
less deeply than that - all but a vanishing minority - is hashed exactly as
|
||||
before, without allocating.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j JSON value to hash
|
||||
@param depth nesting level of @a j, counted from the value passed by the caller
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash(const BasicJsonType& j)
|
||||
std::size_t hash(const BasicJsonType& j, const std::size_t depth = 0)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
@@ -56,22 +70,32 @@ std::size_t hash(const BasicJsonType& j)
|
||||
|
||||
case BasicJsonType::value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j.items())
|
||||
{
|
||||
const auto h = std::hash<string_t> {}(element.key());
|
||||
seed = combine(seed, h);
|
||||
seed = combine(seed, hash(element.value()));
|
||||
seed = combine(seed, hash(element.value(), depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
|
||||
case BasicJsonType::value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
return hash_iteratively(j);
|
||||
}
|
||||
|
||||
auto seed = combine(type, j.size());
|
||||
for (const auto& element : j)
|
||||
{
|
||||
seed = combine(seed, hash(element));
|
||||
seed = combine(seed, hash(element, depth + 1));
|
||||
}
|
||||
return seed;
|
||||
}
|
||||
@@ -127,5 +151,77 @@ std::size_t hash(const BasicJsonType& j)
|
||||
}
|
||||
}
|
||||
|
||||
/// an array or object whose elements @ref hash_iteratively is hashing
|
||||
template<typename BasicJsonType>
|
||||
struct hash_frame
|
||||
{
|
||||
hash_frame(const BasicJsonType* value_, std::size_t seed_) noexcept
|
||||
: value(value_), position(value_->cbegin()), seed(seed_)
|
||||
{}
|
||||
|
||||
const BasicJsonType* value;
|
||||
typename BasicJsonType::const_iterator position;
|
||||
std::size_t seed;
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief hash the array or object @a j without the call stack
|
||||
|
||||
Computes the same value as @ref hash, keeping the arrays and objects it has
|
||||
entered on an explicit stack instead of descending into them. Only reached for
|
||||
values nested deeper than @ref recursion_depth_limit.
|
||||
|
||||
@tparam BasicJsonType basic_json specialization
|
||||
@param j array or object to hash
|
||||
@return hash value of j
|
||||
*/
|
||||
template<typename BasicJsonType>
|
||||
std::size_t hash_iteratively(const BasicJsonType& j)
|
||||
{
|
||||
using string_t = typename BasicJsonType::string_t;
|
||||
|
||||
std::vector<hash_frame<BasicJsonType>> stack;
|
||||
stack.emplace_back(&j, combine(static_cast<std::size_t>(j.type()), j.size()));
|
||||
|
||||
while (true)
|
||||
{
|
||||
// a copy, as entering an element below can reallocate the stack; the
|
||||
// frame itself is only changed through stack.back()
|
||||
const hash_frame<BasicJsonType> frame = stack.back();
|
||||
|
||||
if (frame.position == frame.value->cend())
|
||||
{
|
||||
// all elements are hashed: fold this value's hash into its parent's
|
||||
// seed, exactly where the recursive version returns it
|
||||
const std::size_t h = frame.seed;
|
||||
stack.pop_back();
|
||||
if (stack.empty())
|
||||
{
|
||||
return h;
|
||||
}
|
||||
stack.back().seed = combine(stack.back().seed, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frame.value->is_object())
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, std::hash<string_t> {}(frame.position.key()));
|
||||
}
|
||||
|
||||
// advance before entering the element, which pushes onto the stack
|
||||
const BasicJsonType& element = *frame.position;
|
||||
++stack.back().position;
|
||||
|
||||
if (element.is_structured())
|
||||
{
|
||||
stack.emplace_back(&element, combine(static_cast<std::size_t>(element.type()), element.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.back().seed = combine(stack.back().seed, hash(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <nlohmann/detail/meta/is_sax.hpp>
|
||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -432,7 +433,21 @@ class binary_reader
|
||||
exception_message(input_format_t::bson, concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr));
|
||||
}
|
||||
|
||||
return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast<NumberType>(1), result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(get() != 0x00))
|
||||
{
|
||||
auto last_token = get_token_string();
|
||||
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
|
||||
exception_message(input_format_t::bson,
|
||||
"BSON string is not null-terminated",
|
||||
"string"), nullptr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -550,8 +565,6 @@ class binary_reader
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////
|
||||
// CBOR //
|
||||
//////////
|
||||
@@ -3304,7 +3317,28 @@ class binary_reader
|
||||
const NumberType len,
|
||||
string_t& result)
|
||||
{
|
||||
return get_bytes(format, len, "string", result);
|
||||
// get_bytes() appends to result, and CBOR indefinite-length strings
|
||||
// collect all their chunks in the same result; validating only the
|
||||
// newly read bytes keeps the check linear in the input size
|
||||
const std::size_t old_size = result.size();
|
||||
if (JSON_HEDLEY_UNLIKELY(!get_bytes(format, len, "string", result)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// RFC 8949 (CBOR) §3.1 and the MessagePack/BSON/UBJSON specifications
|
||||
// all require text strings to be valid UTF-8; reject anything else
|
||||
// right here so malformed input is caught at decode time instead of
|
||||
// only surfacing later as a type_error.316 when the value is dumped
|
||||
// (which would defeat allow_exceptions=false / strict discarding).
|
||||
if (JSON_HEDLEY_UNLIKELY(!is_valid_utf8(result, old_size)))
|
||||
{
|
||||
return sax->parse_error(chars_read, get_token_string(),
|
||||
parse_error::create(113, chars_read,
|
||||
exception_message(format, "invalid string: ill-formed UTF-8 byte", "string"), nullptr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -101,6 +101,11 @@ class input_stream_adapter
|
||||
// maintain ifstream flags, except eof
|
||||
if (is != nullptr)
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// consume the character last returned by get_character() unless it
|
||||
// was given back with release_lookahead()
|
||||
commit_lookahead();
|
||||
#endif
|
||||
is->clear(is->rdstate() & std::ios::eofbit);
|
||||
}
|
||||
}
|
||||
@@ -114,6 +119,58 @@ class input_stream_adapter
|
||||
input_stream_adapter& operator=(input_stream_adapter&) = delete;
|
||||
input_stream_adapter& operator=(input_stream_adapter&&) = delete;
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb), lookahead(rhs.lookahead)
|
||||
{
|
||||
rhs.is = nullptr;
|
||||
rhs.sb = nullptr;
|
||||
rhs.lookahead = false;
|
||||
}
|
||||
|
||||
// Whether the character last returned by get_character() can be given back
|
||||
// to the input with release_lookahead().
|
||||
static constexpr bool supports_lookahead = true;
|
||||
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is peeked rather than consumed: it is only stepped over
|
||||
// once the next character is requested, or when the adapter is destroyed.
|
||||
// Until then, release_lookahead() can leave it in the input.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
// step over the character returned by the previous call
|
||||
sb->sbumpc();
|
||||
}
|
||||
|
||||
auto res = sb->sgetc();
|
||||
// set eof manually, as we don't use the istream interface.
|
||||
if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))
|
||||
{
|
||||
// there is nothing to step over next time
|
||||
lookahead = false;
|
||||
is->clear(is->rdstate() | std::ios::eofbit);
|
||||
}
|
||||
else
|
||||
{
|
||||
lookahead = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Leave the character last returned by get_character() in the input, so
|
||||
// that the next read from the stream - by this adapter or by the caller
|
||||
// once parsing is done - sees it again. Unlike putting a consumed
|
||||
// character back, this cannot fail.
|
||||
void release_lookahead() noexcept
|
||||
{
|
||||
lookahead = false;
|
||||
}
|
||||
#else
|
||||
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||
: is(rhs.is), sb(rhs.sb)
|
||||
{
|
||||
@@ -124,6 +181,9 @@ class input_stream_adapter
|
||||
// std::istream/std::streambuf use std::char_traits<char>::to_int_type, to
|
||||
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
||||
//
|
||||
// The character is consumed, so the character that terminates a number
|
||||
// stays consumed after parsing; see JSON_PRECISE_STREAM_POSITION.
|
||||
std::char_traits<char>::int_type get_character()
|
||||
{
|
||||
auto res = sb->sbumpc();
|
||||
@@ -134,10 +194,14 @@ class input_stream_adapter
|
||||
}
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<class T>
|
||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
||||
{
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
commit_lookahead();
|
||||
#endif
|
||||
auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));
|
||||
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||
{
|
||||
@@ -147,9 +211,27 @@ class input_stream_adapter
|
||||
}
|
||||
|
||||
private:
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
// Step over the character last returned by get_character(). The character
|
||||
// has already been peeked successfully, so for every streambuf with a get
|
||||
// area this is a pointer increment that cannot fail.
|
||||
void commit_lookahead()
|
||||
{
|
||||
if (lookahead)
|
||||
{
|
||||
lookahead = false;
|
||||
sb->sbumpc();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// the associated input stream
|
||||
std::istream* is = nullptr;
|
||||
std::streambuf* sb = nullptr;
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
/// whether get_character() peeked a character that is not consumed yet
|
||||
bool lookahead = false;
|
||||
#endif
|
||||
};
|
||||
#endif // JSON_NO_IO
|
||||
|
||||
@@ -762,6 +844,21 @@ contiguous_bytes_input_adapter input_adapter(CharT b)
|
||||
template<typename T, std::size_t N>
|
||||
auto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
|
||||
{
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
// A `char` array from string-literal initialization (e.g. json::parse("123"))
|
||||
// carries a trailing '\0' contributed by the compiler, not by the source
|
||||
// text; drop exactly that one byte so it is not mistaken for real trailing
|
||||
// data. Every other element type (unsigned char, std::uint8_t, ...) keeps
|
||||
// the full extent unconditionally, since a trailing zero byte there is
|
||||
// genuine data (e.g. CBOR/MessagePack). This intentionally does not
|
||||
// strlen()-scan the array (as the pointer overload above does for a
|
||||
// null-delimited string): for a `char` array that is not NUL-terminated
|
||||
// within its bounds, that would read past the end of the array.
|
||||
if (std::is_same<typename std::remove_cv<T>::type, char>::value && N > 0 && array[N - 1] == 0)
|
||||
{
|
||||
return input_adapter(array, array + N - 1);
|
||||
}
|
||||
#endif
|
||||
return input_adapter(array, array + N);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // min
|
||||
#include <algorithm> // find_if, min
|
||||
#include <cstddef>
|
||||
#include <string> // string
|
||||
#include <type_traits> // enable_if_t
|
||||
#include <utility> // move
|
||||
#include <utility> // move, pair
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/detail/exceptions.hpp>
|
||||
@@ -278,7 +278,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -327,7 +327,7 @@ class json_sax_dom_parser
|
||||
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -611,7 +611,7 @@ class json_sax_dom_callback_parser
|
||||
// check object limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive object size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -631,7 +631,17 @@ class json_sax_dom_callback_parser
|
||||
// add discarded value at the given key and store the reference for later
|
||||
if (keep && ref_stack.back())
|
||||
{
|
||||
object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);
|
||||
auto& obj = *ref_stack.back()->m_data.m_value.object;
|
||||
const auto it = obj.find(val);
|
||||
if (it != obj.end())
|
||||
{
|
||||
// this is a duplicate key (legal in JSON); remember its
|
||||
// current value so it can be restored later if the new
|
||||
// value is rejected by the callback, instead of being
|
||||
// erased together with the discarded placeholder
|
||||
duplicate_key_stash.emplace_back(&(it->second), it->second);
|
||||
}
|
||||
object_element = &(obj[val] = discarded);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -643,13 +653,18 @@ class json_sax_dom_callback_parser
|
||||
{
|
||||
if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))
|
||||
{
|
||||
// discard object
|
||||
*ref_stack.back() = discarded;
|
||||
// discard object, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded object.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -663,6 +678,10 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this object is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -711,7 +730,7 @@ class json_sax_dom_callback_parser
|
||||
// check array limit
|
||||
if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))
|
||||
{
|
||||
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
return parse_error(0, "", out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
|
||||
}
|
||||
|
||||
if (len != detail::unknown_size())
|
||||
@@ -743,16 +762,25 @@ class json_sax_dom_callback_parser
|
||||
#endif
|
||||
|
||||
ref_stack.back()->set_parents();
|
||||
// this array is finally, definitively kept; drop any
|
||||
// pending duplicate-key stash entry for its slot since it
|
||||
// can no longer be restored
|
||||
resolve_duplicate_key_stash(ref_stack.back(), false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// discard array
|
||||
*ref_stack.back() = discarded;
|
||||
// discard array, unless this slot holds a duplicate key's
|
||||
// previous value pending restoration, in which case that
|
||||
// value is restored instead of being discarded
|
||||
if (!resolve_duplicate_key_stash(ref_stack.back(), true))
|
||||
{
|
||||
*ref_stack.back() = discarded;
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
// Set start/end positions for discarded array.
|
||||
handle_diagnostic_positions_for_json_value(*ref_stack.back());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +897,35 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
#endif
|
||||
|
||||
/// if there is a pending duplicate-key stash entry for this exact slot,
|
||||
/// remove it from the stash; if restore_value is true, the stashed
|
||||
/// previous value is moved back into the slot first (use this when the
|
||||
/// new value at that slot was rejected); otherwise the stash entry is
|
||||
/// simply dropped (use this when the new value was accepted, so it
|
||||
/// correctly supersedes the old one and no restore should ever happen
|
||||
/// for this slot again)
|
||||
/// @return whether a matching stash entry was found (and processed)
|
||||
bool resolve_duplicate_key_stash(BasicJsonType* slot, bool restore_value)
|
||||
{
|
||||
const auto it = std::find_if(duplicate_key_stash.begin(), duplicate_key_stash.end(),
|
||||
[slot](const std::pair<BasicJsonType*, BasicJsonType>& entry)
|
||||
{
|
||||
return entry.first == slot;
|
||||
});
|
||||
|
||||
if (it == duplicate_key_stash.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (restore_value)
|
||||
{
|
||||
*slot = std::move(it->second);
|
||||
}
|
||||
duplicate_key_stash.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief the key the value now being handled will be stored under
|
||||
|
||||
@@ -887,7 +944,9 @@ class json_sax_dom_callback_parser
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief remove the discarded value the callback rejected from its parent
|
||||
@brief remove the discarded value the callback rejected from its parent,
|
||||
unless it is a duplicate key's slot with a stashed previous value, in
|
||||
which case that previous value is restored instead
|
||||
|
||||
A rejected value can only ever be the one most recently added to @a parent:
|
||||
the last element of an array, or the placeholder key() stored under @a key
|
||||
@@ -902,7 +961,7 @@ class json_sax_dom_callback_parser
|
||||
@param[in,out] parent the container to remove the rejected value from
|
||||
@param[in] key the key the value was stored under; unused for arrays
|
||||
*/
|
||||
static void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
void remove_discarded_value(BasicJsonType& parent, const string_t& key)
|
||||
{
|
||||
if (parent.is_array())
|
||||
{
|
||||
@@ -918,7 +977,12 @@ class json_sax_dom_callback_parser
|
||||
const auto it = object.find(key);
|
||||
if (it != object.end() && it->second.is_discarded())
|
||||
{
|
||||
object.erase(it);
|
||||
// a duplicate key's slot has a stashed previous value that
|
||||
// must be restored instead of being erased
|
||||
if (!resolve_duplicate_key_stash(&it->second, true))
|
||||
{
|
||||
object.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1020,6 +1084,16 @@ class json_sax_dom_callback_parser
|
||||
|
||||
JSON_ASSERT(object_element);
|
||||
*object_element = std::move(value);
|
||||
if (!skip_callback)
|
||||
{
|
||||
// this scalar value finally, definitively replaces whatever was
|
||||
// at this slot; drop any pending duplicate-key stash entry for
|
||||
// it since it can no longer be restored (a container value at
|
||||
// this slot is resolved later, in end_object()/end_array(),
|
||||
// since skip_callback is true for the placeholder handling that
|
||||
// happens here for those)
|
||||
resolve_duplicate_key_stash(object_element, false);
|
||||
}
|
||||
return {true, object_element};
|
||||
}
|
||||
|
||||
@@ -1039,6 +1113,12 @@ class json_sax_dom_callback_parser
|
||||
std::vector<string_t> container_key_stack {}; // NOLINT(readability-redundant-member-init)
|
||||
/// helper to hold the reference for the next object element
|
||||
BasicJsonType* object_element = nullptr;
|
||||
/// stash of (slot pointer, previous value) for object members that
|
||||
/// already existed when key() was called again for the same key
|
||||
/// (duplicate keys); used to restore the previous value if the new
|
||||
/// value is later rejected by the callback, instead of erasing the
|
||||
/// member entirely
|
||||
std::vector<std::pair<BasicJsonType*, BasicJsonType>> duplicate_key_stash {};
|
||||
/// whether a syntax error occurred
|
||||
bool errored = false;
|
||||
/// callback function
|
||||
|
||||
@@ -127,6 +127,25 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter reads with one character of lookahead that
|
||||
// can be left in the input (see input_stream_adapter::supports_lookahead,
|
||||
// which is only defined with JSON_PRECISE_STREAM_POSITION), detected like
|
||||
// supports_seek above.
|
||||
template<typename InputAdapterType>
|
||||
using detect_supports_lookahead = decltype(InputAdapterType::supports_lookahead);
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::true_type /*detected*/)
|
||||
{
|
||||
return InputAdapterType::supports_lookahead;
|
||||
}
|
||||
|
||||
template<typename InputAdapterType>
|
||||
constexpr bool input_adapter_supports_lookahead(std::false_type /*detected*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Detect whether an input adapter exposes a contiguous byte block that the
|
||||
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
||||
// Adapters without the flag - file, stream, wide-string, user-defined - fall
|
||||
@@ -167,6 +186,12 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
static constexpr bool lazy_token_string =
|
||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||
|
||||
/// whether a simulated unget can be passed on to the input adapter, which
|
||||
/// then leaves the character in the input; see
|
||||
/// input_adapter_supports_lookahead
|
||||
static constexpr bool can_release_lookahead =
|
||||
input_adapter_supports_lookahead<InputAdapterType>(is_detected<detect_supports_lookahead, InputAdapterType> {});
|
||||
|
||||
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||
@@ -952,7 +977,9 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
case '\n':
|
||||
case '\r':
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -970,8 +997,10 @@ class lexer : public lexer_base<BasicJsonType>
|
||||
{
|
||||
switch (get())
|
||||
{
|
||||
case char_traits<char_type>::eof():
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
case char_traits<char_type>::eof():
|
||||
{
|
||||
error_message = "invalid comment; missing closing '*/'";
|
||||
return false;
|
||||
@@ -1894,6 +1923,21 @@ scan_number_done:
|
||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
||||
}
|
||||
|
||||
/// adapter without lookahead: nothing to do (see release_lookahead)
|
||||
void release_lookahead_impl(std::false_type /*can_release*/) const noexcept {}
|
||||
|
||||
/// adapter with lookahead: leave the character in the input instead
|
||||
void release_lookahead_impl(std::true_type /*can_release*/)
|
||||
{
|
||||
if (next_unget)
|
||||
{
|
||||
// the character is read from the input again rather than replayed
|
||||
// from current, so the adapter must not step over it
|
||||
next_unget = false;
|
||||
ia.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
/// seekable adapter: nothing was captured, so nothing to undo
|
||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||
|
||||
@@ -1957,6 +2001,31 @@ scan_number_done:
|
||||
return position;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief pass a pending simulated unget on to the input
|
||||
|
||||
unget() only rewinds the lexer's own bookkeeping, so the character that
|
||||
terminated the last token (e.g. the character after a number) would still
|
||||
be stepped over when the input adapter is done. Callers that hand the
|
||||
input back to the user afterwards - operator>> and non-strict sax_parse -
|
||||
call this once when scanning is done, so that the input is positioned
|
||||
right after the value.
|
||||
|
||||
Adapters without lookahead (see input_adapter_supports_lookahead) are not
|
||||
handed back to the user, so this is a no-op for them. Without
|
||||
JSON_PRECISE_STREAM_POSITION, no adapter has lookahead, so this is always a
|
||||
no-op and the terminating character stays consumed.
|
||||
|
||||
Scanning may continue after this call: @a next_unget is cleared, and the
|
||||
character is read from the input again instead of being replayed from
|
||||
@a current. A pending unget of EOF needs no special case, because reaching
|
||||
EOF leaves no lookahead to release.
|
||||
*/
|
||||
void release_lookahead()
|
||||
{
|
||||
release_lookahead_impl(std::integral_constant<bool, can_release_lookahead> {});
|
||||
}
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
/// return the offset of the first character of the last read token; unlike
|
||||
/// the token's parsed value, this accounts for escape sequences
|
||||
@@ -2153,9 +2222,12 @@ scan_number_done:
|
||||
case '9':
|
||||
return scan_number_dispatch(std::integral_constant<bool, bulk_scan> {});
|
||||
|
||||
// end of input (the null byte is needed when parsing from
|
||||
// string literals)
|
||||
#if !JSON_STRICT_NUL_HANDLING
|
||||
case '\0':
|
||||
#endif
|
||||
// end of input; by default, a null byte is also treated as end of
|
||||
// input for backwards compatibility (see JSON_STRICT_NUL_HANDLING
|
||||
// to opt into rejecting a null byte in the input instead)
|
||||
case char_traits<char_type>::eof():
|
||||
return token_type::end_of_input;
|
||||
|
||||
|
||||
@@ -100,13 +100,22 @@ class parser
|
||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
if (strict)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(),
|
||||
exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(),
|
||||
exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
// in case of an error, return a discarded value
|
||||
@@ -128,12 +137,20 @@ class parser
|
||||
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||
sax_parse_internal(&sdp);
|
||||
|
||||
// in strict mode, input must be completely read
|
||||
if (strict && (get_token() != token_type::end_of_input))
|
||||
if (strict)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
// in strict mode, input must be completely read
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
sdp.parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// see above
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
|
||||
// in case of an error, return a discarded value
|
||||
@@ -166,12 +183,24 @@ class parser
|
||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||
const bool result = sax_parse_internal(sax);
|
||||
|
||||
// strict mode: next byte must be EOF
|
||||
if (result && strict && (get_token() != token_type::end_of_input))
|
||||
if (result)
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
if (strict)
|
||||
{
|
||||
// strict mode: next byte must be EOF
|
||||
if (get_token() != token_type::end_of_input)
|
||||
{
|
||||
return sax->parse_error(m_lexer.get_position(),
|
||||
m_lexer.get_token_string(),
|
||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// the caller keeps using the input: position it right after
|
||||
// the value by leaving the character that terminated it
|
||||
m_lexer.release_lookahead();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -186,6 +186,15 @@
|
||||
#define JSON_NO_UNIQUE_ADDRESS
|
||||
#endif
|
||||
|
||||
// Clang targeting MinGW does not survive the thread_local storage the copy
|
||||
// constructor uses to bound its descent: every test that copies a value
|
||||
// segfaults with clang 11.0.1 and clang 18.1.8, while the same tests pass with
|
||||
// GCC targeting MinGW and with every other toolchain the library is tested on.
|
||||
// Copying works the same way without the counter, only more slowly.
|
||||
#if !defined(JSON_NO_THREAD_LOCAL) && defined(__clang__) && defined(__MINGW32__)
|
||||
#define JSON_NO_THREAD_LOCAL 1
|
||||
#endif
|
||||
|
||||
// disable documentation warnings on clang
|
||||
#if defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
@@ -587,16 +596,62 @@ void templated_json_throw(ExceptionType exception)
|
||||
|
||||
#define NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(...) template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> __VA_ARGS__
|
||||
|
||||
// Helpers used to dispatch the NLOHMANN_DEFINE_TYPE_*/NLOHMANN_DEFINE_DERIVED_TYPE_*
|
||||
// macros below between a zero-member and a one-or-more-member implementation
|
||||
// (issue #4041, e.g. NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type) with no further
|
||||
// arguments). NLOHMANN_JSON_TYPE_BODY(Prefix, ...) expands to the macro name
|
||||
// Prefix##EMPTY when __VA_ARGS__ is a single argument (Type alone) and to
|
||||
// Prefix##MEMBERS for two or more (Type, member...). It reuses the existing
|
||||
// 64-slot NLOHMANN_JSON_GET_MACRO dispatch with one extra trailing sentinel
|
||||
// token appended so its own trailing "..." is never left completely empty at
|
||||
// the lowest supported argument count -- invoking a variadic macro so that
|
||||
// "..." matches nothing is only granted unconditionally by the standard since
|
||||
// C++20, and pre-C++20 compilers may reject it under -pedantic regardless of
|
||||
// what the macro body does.
|
||||
//
|
||||
// The EMPTY/MEMBERS suffixes are pasted onto Prefix right in the slot table:
|
||||
// operands of ## are not macro-expanded, so the dispatch keeps working even if
|
||||
// user code defines macros named EMPTY or MEMBERS. Producing the bare suffix
|
||||
// first and pasting it later would let such a macro replace it.
|
||||
//
|
||||
// NLOHMANN_JSON_DERIVED_TYPE_BODY(Prefix, ...) answers the same question for
|
||||
// the derived-type macros, whose fixed prefix is Type,BaseType. It drops the
|
||||
// leading Type and defers to NLOHMANN_JSON_TYPE_BODY rather than shifting the
|
||||
// slot table by one: NLOHMANN_JSON_GET_MACRO only resolves 64 positional
|
||||
// arguments, so dispatching on Type,BaseType,member... directly would run out
|
||||
// one slot early and cap the derived-type macros at 62 members instead of the
|
||||
// 63 that NLOHMANN_JSON_PASTE supports.
|
||||
#define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, \
|
||||
Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## MEMBERS, Prefix ## EMPTY, \
|
||||
NLOHMANN_JSON_TYPE_BODY_SENTINEL))
|
||||
|
||||
#define NLOHMANN_JSON_DERIVED_TYPE_BODY_(Prefix, Type, ...) NLOHMANN_JSON_TYPE_BODY(Prefix, __VA_ARGS__)
|
||||
#define NLOHMANN_JSON_DERIVED_TYPE_BODY(Prefix, ...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY_(Prefix, __VA_ARGS__))
|
||||
|
||||
/*!
|
||||
@brief macro
|
||||
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
|
||||
@since version 3.9.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType&, Type&) noexcept { })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -607,10 +662,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_EMPTY(Type)
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -621,9 +681,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.3
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -633,10 +698,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.9.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type is used as a declarator type, not in an expression */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType&, Type&) noexcept { })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -647,10 +719,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type) NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_EMPTY(Type)
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -661,9 +738,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.11.3
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type&) { nlohmann_json_j = BasicJsonType::object(); })
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_TYPE_BODY(NLOHMANN_JSON_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -673,10 +755,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -687,10 +776,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_EMPTY(Type, BaseType)
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -701,9 +795,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -714,10 +813,17 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); }) \
|
||||
/* NOLINTNEXTLINE(bugprone-macro-parentheses) Type/BaseType are used as declarator types, not in expressions */ \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -728,10 +834,15 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) })
|
||||
|
||||
// identical to NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY: with no members there is nothing to default
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_EMPTY(Type, BaseType) NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_EMPTY(Type, BaseType)
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) }) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT_WITH_NAME, __VA_ARGS__)) })
|
||||
@@ -742,9 +853,14 @@ void templated_json_throw(ExceptionType exception)
|
||||
@since version 3.12.0
|
||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
||||
*/
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_MEMBERS(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) })
|
||||
|
||||
#define NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_EMPTY(Type, BaseType) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); })
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DERIVED_TYPE_BODY(NLOHMANN_JSON_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_, __VA_ARGS__)(__VA_ARGS__))
|
||||
|
||||
#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(Type, BaseType, ...) \
|
||||
NLOHMANN_JSON_BASIC_TYPE_TEMPLATE(void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_DOUBLE_PASTE(NLOHMANN_JSON_TO_WITH_NAME, __VA_ARGS__)) })
|
||||
|
||||
@@ -803,7 +919,3 @@ void templated_json_throw(ExceptionType exception)
|
||||
#ifndef JSON_USE_GLOBAL_UDLS
|
||||
#define JSON_USE_GLOBAL_UDLS 1
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#undef JSON_NO_UNIQUE_ADDRESS
|
||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||
#undef JSON_USE_GLOBAL_UDLS
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
|
||||
#ifndef JSON_TEST_KEEP_MACROS
|
||||
#undef JSON_CATCH
|
||||
@@ -44,6 +43,9 @@
|
||||
#undef JSON_HAS_STD_FORMAT
|
||||
#undef JSON_HAS_STATIC_RTTI
|
||||
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#undef JSON_PRECISE_STREAM_POSITION
|
||||
#undef JSON_STRICT_NUL_HANDLING
|
||||
#endif
|
||||
|
||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
||||
#include <iterator> // back_inserter
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
#include <string> // basic_string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
@@ -44,22 +45,32 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// output adapter for byte vectors
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
///
|
||||
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
|
||||
/// passed to binary_writer by value as a template parameter, so
|
||||
/// write_character()/write_characters() are ordinary (inlinable) calls with no
|
||||
/// vtable lookup and no shared_ptr. It is used for the common
|
||||
/// `to_cbor`/`to_msgpack`/... into a std::vector. output_vector_adapter below
|
||||
/// wraps this same sink to provide the virtual interface.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
class output_vector_sink
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
explicit output_vector_sink(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: v(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
void write_character(CharType c)
|
||||
{
|
||||
v.push_back(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
// no JSON_HEDLEY_NON_NULL here: binary_writer legitimately passes a null
|
||||
// pointer with length 0 for empty strings/binary values. Appending an empty
|
||||
// range is a no-op; the type-erased path tolerates this via the (unattributed)
|
||||
// virtual base, and the concrete sink must do the same.
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
v.insert(v.end(), s, s + length);
|
||||
}
|
||||
@@ -68,6 +79,34 @@ class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
std::vector<CharType, AllocatorType>& v;
|
||||
};
|
||||
|
||||
/// output adapter for byte vectors
|
||||
///
|
||||
/// The appending itself lives in output_vector_sink; this class only adds the
|
||||
/// virtual output_adapter_protocol interface on top of it, so both the
|
||||
/// type-erased and the templated path share one implementation.
|
||||
template<typename CharType, typename AllocatorType = std::allocator<CharType>>
|
||||
class output_vector_adapter : public output_adapter_protocol<CharType>
|
||||
{
|
||||
public:
|
||||
explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept
|
||||
: sink(vec)
|
||||
{}
|
||||
|
||||
void write_character(CharType c) override
|
||||
{
|
||||
sink.write_character(c);
|
||||
}
|
||||
|
||||
JSON_HEDLEY_NON_NULL(2)
|
||||
void write_characters(const CharType* s, std::size_t length) override
|
||||
{
|
||||
sink.write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_vector_sink<CharType, AllocatorType> sink;
|
||||
};
|
||||
|
||||
#ifndef JSON_NO_IO
|
||||
/// output adapter for output streams
|
||||
template<typename CharType>
|
||||
@@ -118,6 +157,39 @@ class output_string_adapter : public output_adapter_protocol<CharType>
|
||||
StringType& str;
|
||||
};
|
||||
|
||||
/// @brief output sink forwarding to a type-erased output adapter
|
||||
///
|
||||
/// Wraps the polymorphic output_adapter_t so the same binary_writer template can
|
||||
/// also target arbitrary adapters (output streams, strings, user-provided
|
||||
/// adapters) via the `output_adapter`-based overloads. Each write still goes
|
||||
/// through one virtual call, exactly as before; only the concrete sinks above
|
||||
/// avoid it.
|
||||
template<typename CharType>
|
||||
class output_adapter_sink
|
||||
{
|
||||
public:
|
||||
explicit output_adapter_sink(output_adapter_t<CharType> adapter)
|
||||
: oa(std::move(adapter))
|
||||
{
|
||||
JSON_ASSERT(oa);
|
||||
}
|
||||
|
||||
void write_character(CharType c)
|
||||
{
|
||||
oa->write_character(c);
|
||||
}
|
||||
|
||||
// no JSON_HEDLEY_NON_NULL: forwards (null, 0) for empty payloads, exactly as
|
||||
// the type-erased path already did before this sink existed
|
||||
void write_characters(const CharType* s, std::size_t length)
|
||||
{
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
};
|
||||
|
||||
template<typename CharType, typename StringType = std::basic_string<CharType>>
|
||||
class output_adapter
|
||||
{
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
#include <nlohmann/detail/meta/cpp_future.hpp>
|
||||
#include <nlohmann/detail/output/binary_writer.hpp>
|
||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||
#include <nlohmann/detail/string_concat.hpp>
|
||||
#include <nlohmann/detail/string_utils.hpp>
|
||||
#include <nlohmann/detail/value_t.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
@@ -57,8 +59,6 @@ class serializer
|
||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
public:
|
||||
/*!
|
||||
@@ -133,7 +133,7 @@ class serializer
|
||||
|
||||
Serializing a container descends into its elements, so a value nested deeply
|
||||
enough used to exhaust the call stack and terminate the process with no
|
||||
exception to catch. The descent is bounded here: once @ref dump_depth_limit
|
||||
exception to catch. The descent is bounded here: once @ref recursion_depth_limit
|
||||
levels have been entered, @ref dump_iteratively writes out what is left
|
||||
without the call stack. A value nested less deeply than that - all but a
|
||||
vanishing minority - is written by exactly the code that always wrote it.
|
||||
@@ -148,7 +148,7 @@ class serializer
|
||||
{
|
||||
case value_t::object:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -223,7 +223,7 @@ class serializer
|
||||
|
||||
case value_t::array:
|
||||
{
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= dump_depth_limit()))
|
||||
if (JSON_HEDLEY_UNLIKELY(depth >= recursion_depth_limit()))
|
||||
{
|
||||
dump_iteratively(val, current_indent);
|
||||
return;
|
||||
@@ -408,19 +408,12 @@ class serializer
|
||||
}
|
||||
|
||||
private:
|
||||
/// the number of levels @ref dump_internal descends into before it hands
|
||||
/// over to @ref dump_iteratively
|
||||
static constexpr std::size_t dump_depth_limit()
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief write out @a val and everything below it without the call stack
|
||||
|
||||
Emits the same bytes as @ref dump_internal, keeping the containers it has
|
||||
entered on an explicit stack instead of descending into them. Only reached
|
||||
for values nested deeper than @ref dump_depth_limit, which is why it is not
|
||||
for values nested deeper than @ref recursion_depth_limit, which is why it is not
|
||||
written for speed: walking every value this way measured up to 20% slower on
|
||||
object-heavy documents than letting the compiler drive the descent.
|
||||
*/
|
||||
@@ -1598,62 +1591,6 @@ class serializer
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string is UTF-8 encoded
|
||||
|
||||
The function checks each byte of a string whether it is UTF-8 encoded. The
|
||||
result of the check is stored in the @a state parameter. The function must
|
||||
be called initially with state 0 (accept). State 1 means the string must
|
||||
be rejected, because the current byte is not allowed. If the string is
|
||||
completely processed, but the state is non-zero, the string ended
|
||||
prematurely; that is, the last byte indicated more bytes should have
|
||||
followed.
|
||||
|
||||
@param[in,out] state the state of the decoding
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note The function has been edited: a std::array is used.
|
||||
|
||||
@copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<size_t>(state) * 16u) + static_cast<size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Overload to make the compiler happy while it is instantiating
|
||||
* dump_integer for number_unsigned_t.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
{
|
||||
|
||||
/*!
|
||||
@brief the number of nesting levels an operation recurses into
|
||||
|
||||
Operations that walk a value (serializing, hashing, merging, ...) recurse once
|
||||
per nesting level, which is fastest, but a value nested deeply enough would
|
||||
exhaust the call stack. So they recurse only this many levels deep and finish
|
||||
whatever lies below with an explicit stack. All of them share this limit.
|
||||
|
||||
@sa https://github.com/nlohmann/json/issues/5387
|
||||
*/
|
||||
constexpr std::size_t recursion_depth_limit() noexcept
|
||||
{
|
||||
return 128;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
@@ -8,10 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t, uint32_t
|
||||
#include <string> // string, to_string
|
||||
|
||||
#include <nlohmann/detail/abi_macros.hpp>
|
||||
#include <nlohmann/detail/macro_scope.hpp>
|
||||
|
||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||
namespace detail
|
||||
@@ -33,5 +36,102 @@ StringType to_string(std::size_t value)
|
||||
return result;
|
||||
}
|
||||
|
||||
///////////////////
|
||||
// UTF-8 decoding //
|
||||
///////////////////
|
||||
|
||||
// UTF-8 decoder states used by decode() below
|
||||
static constexpr std::uint8_t UTF8_ACCEPT = 0;
|
||||
static constexpr std::uint8_t UTF8_REJECT = 1;
|
||||
|
||||
/*!
|
||||
@brief process a byte of a UTF-8 sequence
|
||||
|
||||
This is a single-byte step of a "shift-based" UTF-8 decoder originally
|
||||
written by Björn Hoehrmann. See
|
||||
http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
|
||||
|
||||
This decoder is the single source of truth for UTF-8 validation in this
|
||||
library: it is used both by the serializer (to escape and, in strict mode,
|
||||
reject ill-formed UTF-8 when dumping a string) and by the binary readers
|
||||
(to reject ill-formed UTF-8 in CBOR/MessagePack/BSON/UBJSON text strings at
|
||||
decode time; see @ref is_valid_utf8 below).
|
||||
|
||||
@param[in,out] state the current decoder state
|
||||
@param[in,out] codep codepoint (valid only if resulting state is UTF8_ACCEPT)
|
||||
@param[in] byte next byte to decode
|
||||
@return new state
|
||||
|
||||
@note Original source: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
@sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
*/
|
||||
inline std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept
|
||||
{
|
||||
static const std::array<std::uint8_t, 400> utf8d =
|
||||
{
|
||||
{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F
|
||||
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF
|
||||
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF
|
||||
0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF
|
||||
0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF
|
||||
0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2
|
||||
1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4
|
||||
1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6
|
||||
1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8
|
||||
}
|
||||
};
|
||||
|
||||
JSON_ASSERT(static_cast<std::size_t>(byte) < utf8d.size());
|
||||
const std::uint8_t type = utf8d[byte];
|
||||
|
||||
codep = (state != UTF8_ACCEPT)
|
||||
? (byte & 0x3fu) | (codep << 6u)
|
||||
: (0xFFu >> type) & (byte);
|
||||
|
||||
const std::size_t index = 256u + (static_cast<std::size_t>(state) * 16u) + static_cast<std::size_t>(type);
|
||||
JSON_ASSERT(index < utf8d.size());
|
||||
state = utf8d[index];
|
||||
return state;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief check whether a string consists solely of valid UTF-8
|
||||
|
||||
Used by the CBOR/MessagePack/BSON/UBJSON binary readers to reject text
|
||||
strings that are not valid UTF-8 at decode time (RFC 8949 §3.1 and the
|
||||
MessagePack/BSON specifications all require text strings to be UTF-8), so
|
||||
that malformed input is caught immediately instead of only surfacing later
|
||||
as a type_error.316 when the resulting value is dumped.
|
||||
|
||||
@param[in] s the string to check
|
||||
@param[in] first index of the first byte to check; the bytes before it are
|
||||
assumed to have been validated already and to end on a
|
||||
code point boundary
|
||||
@return whether @a s (from index @a first on) is valid UTF-8
|
||||
*/
|
||||
template<typename StringType>
|
||||
inline bool is_valid_utf8(const StringType& s, const std::size_t first = 0) noexcept
|
||||
{
|
||||
std::uint8_t state = UTF8_ACCEPT;
|
||||
std::uint32_t codepoint = 0;
|
||||
|
||||
for (std::size_t i = first; i < s.size(); ++i)
|
||||
{
|
||||
decode(state, codepoint, static_cast<std::uint8_t>(s[i]));
|
||||
if (state == UTF8_REJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return state == UTF8_ACCEPT;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
NLOHMANN_JSON_NAMESPACE_END
|
||||
|
||||
+1002
-93
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+2466
-502
File diff suppressed because it is too large
Load Diff
@@ -52,6 +52,18 @@
|
||||
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_PRECISE_STREAM_POSITION
|
||||
#define JSON_PRECISE_STREAM_POSITION 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_STRICT_NUL_HANDLING
|
||||
#define JSON_STRICT_NUL_HANDLING 0
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||
#else
|
||||
@@ -70,20 +82,41 @@
|
||||
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
#endif
|
||||
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION _psp
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING _snul
|
||||
#else
|
||||
#define NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING
|
||||
#endif
|
||||
|
||||
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||
#endif
|
||||
|
||||
// Construct the namespace ABI tags component
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f) json_abi ## a ## b ## c ## d ## e ## f
|
||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
|
||||
|
||||
#define NLOHMANN_JSON_ABI_TAGS \
|
||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
|
||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
|
||||
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS, \
|
||||
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
|
||||
|
||||
// Construct the namespace version component
|
||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||
|
||||
@@ -75,7 +75,15 @@ target_compile_options(test_main PUBLIC
|
||||
# is annotated JSON_HEDLEY_NO_RETURN (it always throws), which
|
||||
# makes MSVC flag the code following its call in binary_reader.hpp
|
||||
# as unreachable for that instantiation, in both Debug and Release
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702>
|
||||
# Disable warning C4503: decorated name length exceeded, name was truncated; the deep
|
||||
# copy support added for #5387 pushes the mangled name of
|
||||
# std::allocator_traits<...>::construct for the custom-base-class
|
||||
# test's map type past VS2015's limit. The name is only used for
|
||||
# debug info, so truncation does not affect the build.
|
||||
# Disable warning C5285: cannot declare a specialization for 'std::tuple'; MSVC 19.51
|
||||
# reports the forward declarations of standard library
|
||||
# templates in the vendored doctest.h
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702;/wd4503;/wd5285>
|
||||
# https://github.com/nlohmann/json/issues/1114
|
||||
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
||||
|
||||
|
||||
@@ -14,6 +14,20 @@ add_test(
|
||||
NAME test-abi_config_noversion
|
||||
COMMAND abi_config_noversion ${DOCTEST_TEST_FILTER})
|
||||
|
||||
# test default and no version namespace with all ABI tags enabled, so the
|
||||
# expected tag order is checked regardless of the JSON_* CMake options
|
||||
foreach(test default noversion)
|
||||
add_executable(abi_config_${test}_all_tags ${test}.cpp)
|
||||
target_compile_definitions(abi_config_${test}_all_tags PRIVATE
|
||||
JSON_DIAGNOSTICS=1
|
||||
JSON_DIAGNOSTIC_POSITIONS=1
|
||||
JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)
|
||||
target_link_libraries(abi_config_${test}_all_tags PRIVATE abi_compat_main)
|
||||
add_test(
|
||||
NAME test-abi_config_${test}_all_tags
|
||||
COMMAND abi_config_${test}_all_tags ${DOCTEST_TEST_FILTER})
|
||||
endforeach()
|
||||
|
||||
# test custom namespace
|
||||
add_executable(abi_config_custom custom.cpp)
|
||||
target_link_libraries(abi_config_custom PRIVATE abi_compat_main)
|
||||
|
||||
@@ -24,12 +24,24 @@ TEST_CASE("default namespace")
|
||||
expected += "_diag";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
expected += "_dp";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
expected += "_bics";
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
expected += "_psp";
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
expected += "_snul";
|
||||
#endif
|
||||
|
||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||
|
||||
@@ -25,12 +25,24 @@ TEST_CASE("default namespace without version component")
|
||||
expected += "_diag";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#endif
|
||||
|
||||
#if JSON_DIAGNOSTIC_POSITIONS
|
||||
expected += "_dp";
|
||||
#endif
|
||||
|
||||
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||
expected += "_ldvcmp";
|
||||
#if JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
expected += "_bics";
|
||||
#endif
|
||||
|
||||
#if JSON_PRECISE_STREAM_POSITION
|
||||
expected += "_psp";
|
||||
#endif
|
||||
|
||||
#if JSON_STRICT_NUL_HANDLING
|
||||
expected += "_snul";
|
||||
#endif
|
||||
|
||||
expected += "::basic_json";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.11...3.14)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(JSON_Benchmarks LANGUAGES CXX)
|
||||
|
||||
# set compiler flags
|
||||
@@ -6,29 +6,35 @@ if((CMAKE_CXX_COMPILER_ID MATCHES GNU) OR (CMAKE_CXX_COMPILER_ID MATCHES Clang))
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -DNDEBUG -O3")
|
||||
endif()
|
||||
|
||||
# configure Google Benchmarks
|
||||
# configure Google Benchmark; a fixed release, so that results stay comparable
|
||||
set(JSON_GOOGLE_BENCHMARK_VERSION 1.9.5)
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
benchmark
|
||||
GIT_REPOSITORY https://github.com/google/benchmark.git
|
||||
GIT_TAG origin/main
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
FetchContent_GetProperties(benchmark)
|
||||
if(NOT benchmark_POPULATED)
|
||||
FetchContent_Populate(benchmark)
|
||||
set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "" FORCE)
|
||||
add_subdirectory(${benchmark_SOURCE_DIR} ${benchmark_BINARY_DIR})
|
||||
endif()
|
||||
# only the library is needed; -Werror would break the pinned release as soon as
|
||||
# a newer compiler adds a warning
|
||||
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
||||
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_Declare(benchmark
|
||||
URL https://github.com/google/benchmark/archive/refs/tags/v${JSON_GOOGLE_BENCHMARK_VERSION}.tar.gz
|
||||
URL_HASH SHA256=9631341c82bac4a288bef951f8b26b41f69021794184ece969f8473977eaa340
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(benchmark)
|
||||
|
||||
# download test data
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake ${CMAKE_MODULE_PATH})
|
||||
include(download_test_data)
|
||||
|
||||
# the header to benchmark; point this at a directory holding another version's
|
||||
# nlohmann/json.hpp to compare versions (see README.md)
|
||||
set(JSON_BENCHMARK_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../single_include" CACHE PATH
|
||||
"directory containing the nlohmann/json.hpp to benchmark")
|
||||
|
||||
# benchmark binary
|
||||
add_executable(json_benchmarks src/benchmarks.cpp)
|
||||
target_compile_features(json_benchmarks PRIVATE cxx_std_11)
|
||||
target_link_libraries(json_benchmarks benchmark ${CMAKE_THREAD_LIBS_INIT})
|
||||
add_dependencies(json_benchmarks download_test_data)
|
||||
target_include_directories(json_benchmarks PRIVATE ${CMAKE_SOURCE_DIR}/../../single_include ${CMAKE_BINARY_DIR}/include)
|
||||
target_include_directories(json_benchmarks PRIVATE ${JSON_BENCHMARK_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/include)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Benchmarks
|
||||
|
||||
Micro-benchmarks for parsing, serialization and the binary formats, written with
|
||||
[Google Benchmark](https://github.com/google/benchmark). They are not run by CI; see
|
||||
[When to run them](#when-to-run-them).
|
||||
|
||||
## What is measured
|
||||
|
||||
| benchmark | what it does |
|
||||
|---|---|
|
||||
| `ParseFile`, `ParseString` | parse JSON from a file stream or a string |
|
||||
| `ParseIndented` | parse the large files re-indented by 4 spaces, for the lexer's whitespace handling |
|
||||
| `Dump` | serialize, compact (`-`) and indented (`4`) |
|
||||
| `ToCbor`, `BinaryToCbor` | write CBOR; `BinaryToCbor` writes binary values of growing size |
|
||||
| `FromMsgpack` | read MessagePack; unchanged over the years, so its numbers stay comparable across releases |
|
||||
| `FromBinaryBuffer`, `FromBinaryFile` | read CBOR, MessagePack, UBJSON, BJData and BSON from a buffer or a `FILE*` |
|
||||
| `FromBinaryShape` | read deeply nested, container-heavy and scalar-heavy documents in every binary format |
|
||||
| `FromCborChunkedString` | read CBOR strings split into indefinite-length chunks |
|
||||
|
||||
The input files are those of [nativejson-benchmark](https://github.com/miloyip/nativejson-benchmark) (`canada`,
|
||||
`citm_catalog`, `twitter`), a large `jeopardy` file, and number-heavy files (`floats`, `signed_ints`, ...).
|
||||
`bytes_per_second` counts the bytes read or written: the JSON text when parsing, the output when serializing.
|
||||
|
||||
## Requirements
|
||||
|
||||
- CMake 3.14 or later, a C++11 compiler, and Ninja for the `make` target.
|
||||
- Network access on the first configure: CMake downloads Google Benchmark and the
|
||||
[test data](https://github.com/nlohmann/json_test_data) into the build directory. To reuse a download of the test
|
||||
data, pass `-DJSON_TestDataDirectory=<build directory>/test_files`.
|
||||
- Google Benchmark is pinned to a release (1.9.5), so that results from different days stay comparable. To update it,
|
||||
change `JSON_GOOGLE_BENCHMARK_VERSION` and the archive's `URL_HASH` in `CMakeLists.txt` together.
|
||||
- The benchmarks include `single_include/nlohmann/json.hpp`, so run `make amalgamate` after changing anything in
|
||||
`include/`.
|
||||
|
||||
GCC and Clang builds use `-O3 -flto -DNDEBUG`.
|
||||
|
||||
## Running them
|
||||
|
||||
From the repository root, this builds everything from scratch in `cmake-build-benchmarks` and runs all benchmarks:
|
||||
|
||||
```sh
|
||||
make run_benchmarks
|
||||
```
|
||||
|
||||
To build once and run selectively:
|
||||
|
||||
```sh
|
||||
cmake -S tests/benchmarks -B build-benchmarks -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-benchmarks
|
||||
build-benchmarks/json_benchmarks --benchmark_filter='ParseString|Dump'
|
||||
```
|
||||
|
||||
Useful options of `json_benchmarks`:
|
||||
|
||||
| option | effect |
|
||||
|---|---|
|
||||
| `--benchmark_list_tests` | list the benchmarks instead of running them |
|
||||
| `--benchmark_filter=<regex>` | run only the benchmarks whose names match |
|
||||
| `--benchmark_repetitions=<n>` | run every benchmark `n` times and add mean, median, standard deviation and coefficient of variation |
|
||||
| `--benchmark_enable_random_interleaving=true` | run the repetitions in random order, which spreads out drifts such as thermal throttling |
|
||||
| `--benchmark_min_time=<seconds>s` | run each benchmark at least this long (e.g. `2s`) |
|
||||
| `--benchmark_out=<file> --benchmark_out_format=json` | also write the results to a file, e.g. for `compare.py` |
|
||||
|
||||
## Reading the output
|
||||
|
||||
Each line shows the wall-clock `Time` and the `CPU` time per iteration, the number of `Iterations` Google Benchmark
|
||||
chose, and the throughput in `bytes_per_second`. With repetitions, the lines ending in `_median` are the ones to
|
||||
compare. A `_cv` (coefficient of variation) above a few percent means the machine was too noisy for small
|
||||
differences to mean anything.
|
||||
|
||||
## Comparing two versions
|
||||
|
||||
To see what a change or a release did, build the same benchmarks twice: once against the header of the version to
|
||||
compare with, and once against the current one. `JSON_BENCHMARK_INCLUDE_DIR` names the directory holding the
|
||||
`nlohmann/json.hpp` to benchmark. For example, to compare the current checkout with 3.12.0:
|
||||
|
||||
```sh
|
||||
# the header of the version to compare with
|
||||
mkdir -p build-baseline-header/nlohmann
|
||||
git show v3.12.0:single_include/nlohmann/json.hpp > build-baseline-header/nlohmann/json.hpp
|
||||
|
||||
# the same benchmarks, built against either header
|
||||
cmake -S tests/benchmarks -B build-baseline -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||
-DJSON_BENCHMARK_INCLUDE_DIR="$PWD/build-baseline-header"
|
||||
cmake -S tests/benchmarks -B build-current -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build-baseline
|
||||
cmake --build build-current
|
||||
|
||||
# run both, back to back
|
||||
build-baseline/json_benchmarks --benchmark_repetitions=10 --benchmark_enable_random_interleaving=true \
|
||||
--benchmark_out=build-baseline/results.json --benchmark_out_format=json
|
||||
build-current/json_benchmarks --benchmark_repetitions=10 --benchmark_enable_random_interleaving=true \
|
||||
--benchmark_out=build-current/results.json --benchmark_out_format=json
|
||||
```
|
||||
|
||||
Google Benchmark ships a tool to compare the two result files. It needs NumPy and SciPy:
|
||||
|
||||
```sh
|
||||
python3 -m venv build-venv
|
||||
build-venv/bin/pip install numpy scipy
|
||||
build-venv/bin/python build-current/_deps/benchmark-src/tools/compare.py -a benchmarks build-baseline/results.json build-current/results.json
|
||||
```
|
||||
|
||||
The tool's own `tools/requirements.txt` pins NumPy and SciPy versions that need Python 3.11 or later; with an older
|
||||
Python, unpinned versions work as well. In its output:
|
||||
|
||||
- the `Time` and `CPU` columns are relative changes: `-0.35` means 35% faster, `+0.10` means 10% slower;
|
||||
- `_pvalue` lines report a Mann-Whitney U test of whether the two versions differ. It needs at least 9
|
||||
repetitions, and a p-value below 0.05 means the difference is unlikely to be noise;
|
||||
- `OVERALL_GEOMEAN` summarizes all benchmarks;
|
||||
- `-a` shows only the aggregates, not every repetition.
|
||||
|
||||
The header you compare with must support everything the benchmarks use. The current benchmarks build against 3.12.0.
|
||||
Only benchmarks present in both result files are compared, so for older releases, either filter the benchmarks or
|
||||
build that release's own `tests/benchmarks` against its own header.
|
||||
|
||||
## Getting stable numbers
|
||||
|
||||
- Build and run both versions on the same machine, one right after the other.
|
||||
- Keep the machine otherwise idle: no builds, no browser, and a laptop plugged in.
|
||||
- On Linux, set the CPU frequency governor to `performance`, e.g. `sudo cpupower frequency-set --governor performance`.
|
||||
Google Benchmark prints a warning when frequency scaling is enabled. Pinning the process to a core
|
||||
(`taskset -c 2 ...`) helps as well.
|
||||
- Use 10 or more repetitions with random interleaving, compare medians, and treat changes within the `_cv` as noise.
|
||||
|
||||
## When to run them
|
||||
|
||||
They are a manual step, not part of CI: shared CI runners vary more between runs than most of the effects measured.
|
||||
Run the comparison above before a release, comparing the previous release tag with `develop`, and for pull requests
|
||||
that claim to change performance.
|
||||
@@ -131,7 +131,8 @@ static void Dump(benchmark::State& state, const char* filename, int indent)
|
||||
|
||||
while (state.KeepRunning())
|
||||
{
|
||||
j.dump(indent);
|
||||
std::string output = j.dump(indent);
|
||||
benchmark::DoNotOptimize(output);
|
||||
}
|
||||
|
||||
state.SetBytesProcessed(state.iterations() * j.dump(indent).size());
|
||||
|
||||
@@ -79,3 +79,26 @@ the same `fuzzers` target as above and also relies on the `FUZZER_ENGINE` variab
|
||||
[build script](https://github.com/google/oss-fuzz/blob/master/projects/json/build.sh) for more information.
|
||||
|
||||
In case the build at OSS-Fuzz fails, an issue will be created automatically.
|
||||
|
||||
### Handling OSS-Fuzz reports
|
||||
|
||||
OSS-Fuzz files the crashes it finds in its own [issue tracker](https://issues.oss-fuzz.com), not on GitHub. So that
|
||||
each report can be traced to the change that fixed it, and each fix to the report it answers, fixes follow these
|
||||
conventions:
|
||||
|
||||
- **Reference the OSS-Fuzz issue in the pull request**, next to any GitHub issue it closes, as `OSS-Fuzz: <id>` (for
|
||||
example, `OSS-Fuzz: 563659413`), and in the commit message. The ID alone does not disclose the crash. If the report
|
||||
was triaged into a GitHub issue, link the OSS-Fuzz issue there too.
|
||||
- **Turn the reproducer into a unit test.** Download the testcase from the OSS-Fuzz report, reduce it if possible, and
|
||||
add it as a regression test to the unit test of the affected format (e.g., `tests/src/unit-bjdata.cpp`), with a
|
||||
comment naming the OSS-Fuzz issue. This way the input is checked by every CI run rather than only by OSS-Fuzz, and
|
||||
it stays covered even if OSS-Fuzz later closes the report as not reproducible.
|
||||
- **Keep the fuzzer drivers and the unit tests in sync.** The round-trip checks of the UBJSON and BJData drivers are
|
||||
also run on a fixed corpus in the unit tests (see `tests/src/round_trip_corpus.hpp` and the "round-trip invariants"
|
||||
test cases), so a regression shows up in CI first. When a driver's checks change, change the unit tests with them.
|
||||
- **Record in the report whether the bug shipped.** OSS-Fuzz asks whether a crash was a short-lived regression or
|
||||
affects a released version; answer it when the fix is merged, as it decides whether the fix needs a release note or
|
||||
a security advisory (see the [security policy](../.github/SECURITY.md)).
|
||||
|
||||
After the fix is merged, OSS-Fuzz re-runs the reproducer on its next build and marks the report as verified and
|
||||
closed. If it does not, the fix is incomplete.
|
||||
|
||||
@@ -21,16 +21,53 @@ array data, it performs the following steps:
|
||||
- j4 = from_bjdata(vec3)
|
||||
- assert(j1 == j4)
|
||||
|
||||
Re-serializing j2/j3/j4 with the same use_size/use_type settings is checked
|
||||
for value-stability rather than byte-exact stability: from_bjdata(to_bjdata(j2))
|
||||
must equal j2 (and likewise for j3, j4). Byte-exact stability does not hold in
|
||||
general, because a BJData value can lose type fidelity across a round trip
|
||||
(e.g. a binary_t value serialized without the optimized "$U#" array header is
|
||||
parsed back as a plain array of numbers, see #5398 and the discussion on
|
||||
PR #5494) - the numeric value is preserved, but the writer's smallest-type
|
||||
selection for the now-plain numbers may legitimately pick a different, but
|
||||
equally valid, single-byte type marker than the dedicated binary-data writer
|
||||
would have. Both encodings are valid BJData and both decode to the same
|
||||
value, so this is not treated as a round-trip failure here.
|
||||
|
||||
"Value-stable" is checked by comparing dump()s rather than with operator==
|
||||
directly: a BJData/UBJSON payload can decode to a non-finite double (NaN or
|
||||
+-Infinity), and IEEE 754 NaN is never equal to itself, so operator== would
|
||||
report two structurally-identical trees as different whenever a NaN is
|
||||
involved -- not a round-trip bug, just NaN's ordinary (non-)reflexivity.
|
||||
dump() serializes any non-finite double the same deterministic way (as JSON
|
||||
`null`, since JSON itself cannot represent NaN/Infinity), so comparing
|
||||
dumps is stable under exactly the same values that break operator==.
|
||||
|
||||
The unit tests run the same checks on a fixed corpus (see the "BJData round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// value-stable comparison for the round-trip checks below; see the note
|
||||
// above on why this compares dump()s rather than the json values directly
|
||||
static bool is_value_stable(const json& lhs, const json& rhs)
|
||||
{
|
||||
return lhs.dump() == rhs.dump();
|
||||
}
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
{
|
||||
@@ -56,10 +93,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
|
||||
json const j3 = json::from_bjdata(vec3);
|
||||
json const j4 = json::from_bjdata(vec4);
|
||||
|
||||
// serializations must match
|
||||
assert(json::to_bjdata(j2, false, false) == vec2);
|
||||
assert(json::to_bjdata(j3, true, false) == vec3);
|
||||
assert(json::to_bjdata(j4, true, true) == vec4);
|
||||
// re-serializing must be value-stable (see the notes above on
|
||||
// why byte-exact stability is not guaranteed in general, and
|
||||
// why this compares dump()s rather than the values directly)
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j2, false, false)), j2));
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j3, true, false)), j3));
|
||||
assert(is_value_stable(json::from_bjdata(json::to_bjdata(j4, true, true)), j4));
|
||||
}
|
||||
catch (const json::parse_error&)
|
||||
{
|
||||
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -20,10 +20,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -19,10 +19,16 @@ The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -21,14 +21,23 @@ array data, it performs the following steps:
|
||||
- j4 = from_ubjson(vec3)
|
||||
- assert(j1 == j4)
|
||||
|
||||
The unit tests run the same checks on a fixed corpus (see the "UBJSON round-trip
|
||||
invariants" test case), so keep both in sync.
|
||||
|
||||
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||
drivers.
|
||||
*/
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// the round-trip checks below are assertions; NDEBUG would compile them away
|
||||
#ifdef NDEBUG
|
||||
#error "the fuzzer drivers must be built without NDEBUG"
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
// see http://llvm.org/docs/LibFuzzer.html
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cmath> // nan
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // int32_t, int64_t, uint32_t, uint64_t
|
||||
#include <limits> // numeric_limits
|
||||
#include <random> // mt19937
|
||||
#include <string> // string, to_string
|
||||
#include <utility> // move
|
||||
#include <vector> // vector
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
// Values for the round-trip property tests of the UBJSON and BJData writers.
|
||||
//
|
||||
// The fuzzer drivers (tests/src/fuzzer-parse_ubjson.cpp and
|
||||
// fuzzer-parse_bjdata.cpp) check that anything the library parses can be
|
||||
// serialized, parsed back, and serialized again without loss. Those checks
|
||||
// only run at OSS-Fuzz, so a regression used to surface days later as an
|
||||
// external report. The unit tests run the same checks on this corpus in CI.
|
||||
//
|
||||
// The corpus is deterministic: std::mt19937's output sequence is fixed by
|
||||
// the standard, and it is used directly rather than through a distribution
|
||||
// (whose results are implementation-defined).
|
||||
namespace utils
|
||||
{
|
||||
|
||||
class round_trip_corpus
|
||||
{
|
||||
public:
|
||||
using json = nlohmann::json;
|
||||
|
||||
static std::vector<json> values()
|
||||
{
|
||||
round_trip_corpus corpus;
|
||||
return corpus.build();
|
||||
}
|
||||
|
||||
// whether a value contains a binary value, which a BJData or UBJSON round
|
||||
// trip may turn into an array of integers
|
||||
static bool contains_binary(const json& j)
|
||||
{
|
||||
if (j.is_binary())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (j.is_structured())
|
||||
{
|
||||
for (const auto& element : j)
|
||||
{
|
||||
if (contains_binary(element))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<json> atoms;
|
||||
// a fixed seed is the point: the corpus must be the same in every run
|
||||
std::mt19937 generator{42}; // NOLINT(cert-msc32-c,cert-msc51-cpp,bugprone-random-generator-seed)
|
||||
|
||||
round_trip_corpus()
|
||||
: atoms
|
||||
{
|
||||
nullptr, true, false,
|
||||
// integers at the boundaries of every UBJSON/BJData integer type
|
||||
0, 1, -1, 127, 128, 255, 256, -128, -129,
|
||||
32767, 32768, 65535, 65536, -32768, -32769,
|
||||
(std::numeric_limits<std::int32_t>::min)(), (std::numeric_limits<std::int32_t>::max)(),
|
||||
(std::numeric_limits<std::uint32_t>::max)(),
|
||||
(std::numeric_limits<std::int64_t>::min)(), (std::numeric_limits<std::int64_t>::max)(),
|
||||
static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()) + 1u,
|
||||
(std::numeric_limits<std::uint64_t>::max)(),
|
||||
// floating-point numbers, including non-finite ones
|
||||
0.0, -0.0, 1.5, -2.25, 3.4e38, (std::numeric_limits<double>::max)(),
|
||||
std::nan(""), std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity(),
|
||||
// strings, including a non-ASCII one and one longer than 255 bytes
|
||||
"", "a", "\xC3\xA4", std::string(300, 'x'),
|
||||
// binary values with and without subtype
|
||||
json::binary({}), json::binary({1, 2, 255}), json::binary({0x80, 0x7F}, 42), json::binary({1}, 0)
|
||||
}
|
||||
{}
|
||||
|
||||
std::vector<json> build()
|
||||
{
|
||||
std::vector<json> result = atoms;
|
||||
|
||||
// each atom inside containers, including homogeneous ones that the
|
||||
// writers encode as optimized (typed) containers
|
||||
result.emplace_back(json::array());
|
||||
result.emplace_back(json::object());
|
||||
for (const auto& atom : atoms)
|
||||
{
|
||||
result.push_back(json::array({atom}));
|
||||
result.push_back(json::array({atom, atom, atom}));
|
||||
result.push_back(json::array({json::array({atom})}));
|
||||
result.push_back(json::object({{"key", atom}}));
|
||||
}
|
||||
result.push_back(json::array({1, 1.5}));
|
||||
result.push_back(json::array({-1, 255}));
|
||||
result.push_back(json::array({"a", "b"}));
|
||||
|
||||
// deep, but well below any recursion or depth limit
|
||||
json nested_array = 1;
|
||||
json nested_object = 1;
|
||||
for (int i = 0; i < 300; ++i)
|
||||
{
|
||||
nested_array = json::array({nested_array});
|
||||
nested_object = json::object({{"key", nested_object}});
|
||||
}
|
||||
result.push_back(nested_array);
|
||||
result.push_back(nested_object);
|
||||
|
||||
add_annotated_arrays(result);
|
||||
add_random_values(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// objects in the JData annotated array format, which the BJData writer
|
||||
// encodes as ND-arrays when the annotation describes a packed array, and
|
||||
// as plain objects otherwise (see #5398, #5399, #5403, #5404, and #5542)
|
||||
static void add_annotated_arrays(std::vector<json>& result)
|
||||
{
|
||||
const std::vector<json> types =
|
||||
{
|
||||
"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64",
|
||||
"single", "double", "char", "byte", "bool", "unknown", 5, nullptr
|
||||
};
|
||||
const std::vector<json> sizes =
|
||||
{
|
||||
json::array(), {3}, {1, 3}, {3, 1}, {2, 3}, {2, 0}, {0, 2}, {2, 2, 2}, {-1, 2}, {2, 1.5},
|
||||
"3", 3, nullptr, json::binary({})
|
||||
};
|
||||
const std::vector<json> data =
|
||||
{
|
||||
nullptr, 5, "s", json::object({{"a", 1}}), json::array(),
|
||||
{1, 2, 3}, {1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6, 7, 8},
|
||||
{1.5, 2.5, 3.5, 4.5, 5.5, 6.5}, {300, -300, 70000, -70000, 1, 2},
|
||||
{"a", "b", "c", "d", "e", "f"}, {json::array({1, 2, 3}), json::array({4, 5, 6})}
|
||||
};
|
||||
|
||||
for (const auto& type : types)
|
||||
{
|
||||
for (const auto& size : sizes)
|
||||
{
|
||||
for (const auto& d : data)
|
||||
{
|
||||
result.push_back({{"_ArrayType_", type}, {"_ArraySize_", size}, {"_ArrayData_", d}});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// incomplete annotations and annotations with an extra key
|
||||
result.push_back({{"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
result.push_back({{"_ArrayType_", "uint8"}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
result.push_back({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}});
|
||||
result.push_back({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}, {"extra", 1}});
|
||||
}
|
||||
|
||||
// random containers of atoms, both homogeneous and mixed
|
||||
void add_random_values(std::vector<json>& result)
|
||||
{
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
result.push_back(random_value(0));
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t random_below(std::size_t bound)
|
||||
{
|
||||
return generator() % bound;
|
||||
}
|
||||
|
||||
json random_value(int depth)
|
||||
{
|
||||
const auto kind = random_below(10);
|
||||
if (depth > 3 || kind < 5)
|
||||
{
|
||||
return atoms[random_below(atoms.size())];
|
||||
}
|
||||
|
||||
json result = kind < 8 ? json::array() : json::object();
|
||||
const auto count = random_below(5);
|
||||
const bool homogeneous = random_below(2) == 0;
|
||||
const json fixed = atoms[random_below(atoms.size())];
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
json element = homogeneous ? fixed : random_value(depth + 1);
|
||||
if (result.is_array())
|
||||
{
|
||||
result.push_back(std::move(element));
|
||||
}
|
||||
else
|
||||
{
|
||||
result[std::to_string(i)] = std::move(element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace utils
|
||||
@@ -0,0 +1,61 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Standalone compile-and-run check for the JSON_SKIP_LIBRARY_VERSION_CHECK
|
||||
// configuration macro, which (per #5423) was never exercised anywhere in the
|
||||
// test matrix.
|
||||
//
|
||||
// include/nlohmann/detail/abi_macros.hpp normally emits a #warning if
|
||||
// NLOHMANN_JSON_VERSION_MAJOR/MINOR/PATCH are already defined (as they would
|
||||
// be by an earlier inclusion of a different version of the library) with
|
||||
// values that mismatch the version about to be defined -- unless
|
||||
// JSON_SKIP_LIBRARY_VERSION_CHECK is defined, in which case the check (and
|
||||
// that #warning) is skipped.
|
||||
//
|
||||
// This file deliberately is not named tests/src/unit-*.cpp: it is compiled
|
||||
// directly (with a modest, non-strict warning set) by the dedicated
|
||||
// ci_test_skiplibraryversioncheck target in cmake/ci.cmake, rather than being
|
||||
// folded into the library's own -Weverything/-Werror unit test matrix. That
|
||||
// is because the scenario simulated here -- mixing two different, already
|
||||
// differently-versioned inclusions of the library in one translation unit --
|
||||
// unavoidably also triggers the *compiler's own* "macro redefined" warning,
|
||||
// independent of (and unaffected by) JSON_SKIP_LIBRARY_VERSION_CHECK, which
|
||||
// only ever silences the library's own #warning. Building this file under
|
||||
// -Weverything -Werror would therefore fail for a reason unrelated to the
|
||||
// macro under test.
|
||||
#define NLOHMANN_JSON_VERSION_MAJOR 0
|
||||
#define NLOHMANN_JSON_VERSION_MINOR 0
|
||||
#define NLOHMANN_JSON_VERSION_PATCH 0
|
||||
|
||||
#define JSON_SKIP_LIBRARY_VERSION_CHECK 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
// reaching this point at all already proves that the mismatched,
|
||||
// pre-defined version macros above did not stop compilation -- which is
|
||||
// exactly what JSON_SKIP_LIBRARY_VERSION_CHECK is for. The library must
|
||||
// also still be fully usable.
|
||||
const nlohmann::json j = {{"a", 1}, {"b", {1, 2, 3}}};
|
||||
if (j.dump() != "{\"a\":1,\"b\":[1,2,3]}")
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
// include/nlohmann/detail/abi_macros.hpp unconditionally (re)defines the
|
||||
// version macros to the library's real, current version right after the
|
||||
// (here, skipped) mismatch check, regardless of the deliberately wrong
|
||||
// stand-in values defined above.
|
||||
if (NLOHMANN_JSON_VERSION_MAJOR == 0 && NLOHMANN_JSON_VERSION_MINOR == 0 && NLOHMANN_JSON_VERSION_PATCH == 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -216,9 +216,136 @@ TEST_CASE("controlled bad_alloc")
|
||||
CHECK_THROWS_AS(my_json(s), std::bad_alloc&);
|
||||
next_construct_fails = false;
|
||||
}
|
||||
|
||||
SECTION("basic_json(const basic_json&) of a deeply nested value (#5387)")
|
||||
{
|
||||
// Copying a value nested deeper than the descent bound builds the
|
||||
// copy from the top down: every value whose own copy has not been
|
||||
// made yet stays a null value until it is. Failing an allocation
|
||||
// part-way through is what proves such a half-built copy can still
|
||||
// be destroyed.
|
||||
//
|
||||
// Which path the failure lands in depends on the build: the first
|
||||
// allocation of a copy belongs to the outermost level, so here it
|
||||
// is the descending one. Built with JSON_NO_THREAD_LOCAL - as the
|
||||
// ci_test_no_thread_local target builds the whole suite - no
|
||||
// descent is made at all and the very same failure lands in the
|
||||
// iterative path instead, part-way through its worklist.
|
||||
const auto check_deep_copy = [](bool objects)
|
||||
{
|
||||
CAPTURE(objects);
|
||||
|
||||
next_construct_fails = false;
|
||||
|
||||
// deeper than the 128 levels the copy constructor descends into
|
||||
const std::size_t depth = 300;
|
||||
|
||||
my_json j = 1;
|
||||
for (std::size_t i = 0; i < depth; ++i)
|
||||
{
|
||||
if (objects)
|
||||
{
|
||||
my_json wrapper = my_json::object();
|
||||
wrapper["a"] = std::move(j);
|
||||
j = std::move(wrapper);
|
||||
}
|
||||
else
|
||||
{
|
||||
j = my_json::array({std::move(j)});
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
CHECK_NOTHROW(my_json(j));
|
||||
|
||||
next_construct_fails = true;
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
CHECK_THROWS_AS(my_json(j), std::bad_alloc&);
|
||||
next_construct_fails = false;
|
||||
};
|
||||
|
||||
check_deep_copy(false);
|
||||
check_deep_copy(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// counts the allocations of pairs with a non-const first member: the object
|
||||
// types store std::pair<const Key, T>, so only the scratch space of the
|
||||
// iterative deep copy allocates std::pair<Key, T>
|
||||
std::size_t scratch_pair_allocations = 0;
|
||||
|
||||
template<class T>
|
||||
struct is_scratch_pair : std::false_type {};
|
||||
|
||||
template<class K, class V>
|
||||
struct is_scratch_pair<std::pair<K, V>> : std::integral_constant < bool, !std::is_const<K>::value > {};
|
||||
|
||||
template<class T>
|
||||
struct scratch_counting_allocator : std::allocator<T>
|
||||
{
|
||||
using std::allocator<T>::allocator;
|
||||
|
||||
T* allocate(std::size_t n)
|
||||
{
|
||||
if (is_scratch_pair<T>::value)
|
||||
{
|
||||
++scratch_pair_allocations;
|
||||
}
|
||||
return std::allocator<T>::allocate(n);
|
||||
}
|
||||
|
||||
#ifdef __cpp_lib_allocate_at_least
|
||||
// std::allocator<T>::allocate_at_least would bypass the counting, and
|
||||
// libc++'s containers prefer it over allocate from C++23 on
|
||||
auto allocate_at_least(std::size_t n)
|
||||
{
|
||||
if (is_scratch_pair<T>::value)
|
||||
{
|
||||
++scratch_pair_allocations;
|
||||
}
|
||||
return std::allocator<T>::allocate_at_least(n);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class U>
|
||||
struct rebind
|
||||
{
|
||||
using other = scratch_counting_allocator<U>;
|
||||
};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("deep copy uses the provided allocator")
|
||||
{
|
||||
using counting_json = nlohmann::basic_json<std::map,
|
||||
std::vector,
|
||||
std::string,
|
||||
bool,
|
||||
std::int64_t,
|
||||
std::uint64_t,
|
||||
double,
|
||||
scratch_counting_allocator>;
|
||||
|
||||
// deeper than the 128 levels the copy constructor descends into, so the
|
||||
// innermost objects are copied by the iterative deep copy
|
||||
counting_json j = 1;
|
||||
for (std::size_t i = 0; i < 300; ++i)
|
||||
{
|
||||
counting_json wrapper = counting_json::object();
|
||||
wrapper["a"] = std::move(j);
|
||||
j = std::move(wrapper);
|
||||
}
|
||||
|
||||
scratch_pair_allocations = 0;
|
||||
// NOLINTNEXTLINE(performance-unnecessary-copy-initialization): the copy is what is tested
|
||||
const counting_json copy(j);
|
||||
CHECK(scratch_pair_allocations > 0);
|
||||
CHECK(copy == j);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template<class T>
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// a spread of values exercising every writer path: scalars of each width, the
|
||||
// float paths, strings, binary, and containers big enough to reallocate
|
||||
std::vector<json> test_values()
|
||||
{
|
||||
json big_array = json::array();
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
big_array.push_back(i);
|
||||
}
|
||||
|
||||
json big_object = json::object();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
big_object[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
json(nullptr), json(true), json(false),
|
||||
json(0), json(-1), json(255), json(-129), json(65535), json(-32769),
|
||||
json(4294967295U), json(-2147483649LL), json(18446744073709551615ULL),
|
||||
json(0.0), json(-0.5), json(3.1415926535897932),
|
||||
json(""), json("hello"), json(std::string(1000, 'x')),
|
||||
json::binary({0x00, 0x01, 0x02}, 42),
|
||||
json::array(), json::object(),
|
||||
json::array({1, 2, 3}), json({{"a", 1}, {"b", nullptr}}),
|
||||
json({{"nested", {{"deep", json::array({1, "two", 3.0, nullptr})}}}}),
|
||||
big_array, big_object
|
||||
};
|
||||
}
|
||||
|
||||
// values to_bson() accepts: the document must be an object
|
||||
std::vector<json> bson_values()
|
||||
{
|
||||
json big_object = json::object();
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
{
|
||||
big_object[std::to_string(i)] = i;
|
||||
}
|
||||
|
||||
return
|
||||
{
|
||||
json::object(),
|
||||
json({{"a", 1}, {"b", nullptr}, {"c", true}, {"d", 2.5}, {"e", "text"}}),
|
||||
json({{"arr", json::array({1, 2, 3})}, {"obj", {{"k", "v"}}}}),
|
||||
big_object
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The vector-returning to_*(j) overloads write through the non-virtual
|
||||
// output_vector_sink, while to_*(j, adapter) goes through output_adapter_sink.
|
||||
// The two are separate code paths that must stay byte-for-byte identical; these
|
||||
// checks fail if either overload is ever changed without the other.
|
||||
TEST_CASE("binary writer output sinks")
|
||||
{
|
||||
SECTION("vector sink and adapter sink agree")
|
||||
{
|
||||
// note: no SUBCASE inside these loops - doctest keys subcases by
|
||||
// name/file/line, so a subcase in a loop body would only ever run for
|
||||
// the first iteration
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
std::vector<std::uint8_t> cbor;
|
||||
json::to_cbor(j, cbor);
|
||||
CHECK(json::to_cbor(j) == cbor);
|
||||
|
||||
std::vector<std::uint8_t> msgpack;
|
||||
json::to_msgpack(j, msgpack);
|
||||
CHECK(json::to_msgpack(j) == msgpack);
|
||||
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
for (const bool use_type :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
if (use_type && !use_size)
|
||||
{
|
||||
continue; // not a supported combination
|
||||
}
|
||||
CAPTURE(use_size);
|
||||
CAPTURE(use_type);
|
||||
std::vector<std::uint8_t> ubjson;
|
||||
json::to_ubjson(j, ubjson, use_size, use_type);
|
||||
CHECK(json::to_ubjson(j, use_size, use_type) == ubjson);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto version :
|
||||
{
|
||||
json::bjdata_version_t::draft2, json::bjdata_version_t::draft3
|
||||
})
|
||||
{
|
||||
std::vector<std::uint8_t> bjdata;
|
||||
json::to_bjdata(j, bjdata, false, false, version);
|
||||
CHECK(json::to_bjdata(j, false, false, version) == bjdata);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
std::vector<std::uint8_t> bson;
|
||||
json::to_bson(j, bson);
|
||||
CHECK(json::to_bson(j) == bson);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("the char adapter produces the same bytes")
|
||||
{
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
const std::vector<std::uint8_t> expected = json::to_cbor(j);
|
||||
std::vector<char> as_char;
|
||||
json::to_cbor(j, as_char);
|
||||
|
||||
REQUIRE(as_char.size() == expected.size());
|
||||
std::vector<std::uint8_t> as_bytes;
|
||||
as_bytes.reserve(as_char.size());
|
||||
for (const char c : as_char)
|
||||
{
|
||||
as_bytes.push_back(static_cast<std::uint8_t>(c));
|
||||
}
|
||||
CHECK(as_bytes == expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// binary_reserve_hint() is documented as a *lower* bound on the serialized size,
|
||||
// so that reserving it up front can never leave the returned vector holding
|
||||
// capacity beyond what the value actually needs.
|
||||
TEST_CASE("binary_reserve_hint never over-reserves")
|
||||
{
|
||||
for (const auto& j : test_values())
|
||||
{
|
||||
CAPTURE(j.dump(-1, ' ', false, json::error_handler_t::replace));
|
||||
|
||||
const std::size_t hint = nlohmann::detail::binary_reserve_hint(j);
|
||||
|
||||
CHECK(hint <= json::to_cbor(j).size());
|
||||
CHECK(hint <= json::to_msgpack(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j).size());
|
||||
CHECK(hint <= json::to_ubjson(j, true, true).size());
|
||||
CHECK(hint <= json::to_bjdata(j).size());
|
||||
}
|
||||
|
||||
for (const auto& j : bson_values())
|
||||
{
|
||||
CAPTURE(j.dump());
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(j) <= json::to_bson(j).size());
|
||||
}
|
||||
|
||||
SECTION("scalars get no hint")
|
||||
{
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json(nullptr)) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json(42)) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json("a string")) == 0);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::binary({0x01})) == 0);
|
||||
}
|
||||
|
||||
SECTION("containers are hinted from their element count")
|
||||
{
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::array()) == 1);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::array({1, 2, 3})) == 4);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json::object()) == 1);
|
||||
CHECK(nlohmann::detail::binary_reserve_hint(json({{"a", 1}, {"b", 2}})) == 5);
|
||||
}
|
||||
}
|
||||
+320
-21
@@ -19,6 +19,7 @@ using nlohmann::json;
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
#include "make_test_data_available.hpp"
|
||||
#include "round_trip_corpus.hpp"
|
||||
#include "test_utils.hpp"
|
||||
|
||||
namespace
|
||||
@@ -2586,7 +2587,12 @@ TEST_CASE("BJData")
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_d), true, true) == v_d);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_D), true, true) == v_D);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_C), true, true) == v_C);
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true) == v_B);
|
||||
// v_B uses the Draft-3-only 'B' marker, so it round-trips only when
|
||||
// Draft 3 is explicitly selected (see GitHub issue #5404); the
|
||||
// default Draft 2 falls back to a plain object instead, covered by
|
||||
// the "ndarray with _ArrayType_ "byte" is gated by the BJData draft
|
||||
// version" section below
|
||||
CHECK(json::to_bjdata(json::from_bjdata(v_B), true, true, json::bjdata_version_t::draft3) == v_B);
|
||||
}
|
||||
|
||||
SECTION("ndarray with data not matching _ArrayType_ is written as an object")
|
||||
@@ -2599,25 +2605,25 @@ TEST_CASE("BJData")
|
||||
// that still round-trips.
|
||||
|
||||
// string data declared as a uint64 array
|
||||
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {1}}, {"_ArrayData_", {"pointer"}}});
|
||||
json const j_str = json({{"_ArrayType_", "uint64"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {"pointer", "value"}}});
|
||||
const auto out_str = json::to_bjdata(j_str);
|
||||
CHECK(out_str.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_str) == j_str);
|
||||
|
||||
// integer data declared as a double array
|
||||
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
json const j_float = json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_float = json::to_bjdata(j_float);
|
||||
CHECK(out_float.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_float) == j_float);
|
||||
|
||||
// a non-integer shape entry is likewise not treated as an ndarray
|
||||
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x"}}, {"_ArrayData_", {1}}});
|
||||
json const j_size = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {"x", 1}}, {"_ArrayData_", {1}}});
|
||||
const auto out_size = json::to_bjdata(j_size);
|
||||
CHECK(out_size.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_size) == j_size);
|
||||
|
||||
// a negative shape entry is not a usable dimension either
|
||||
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1],"_ArrayData_":[1]})");
|
||||
json const j_neg = json::parse(R"({"_ArrayType_":"uint8","_ArraySize_":[-1,1],"_ArrayData_":[1]})");
|
||||
const auto out_neg = json::to_bjdata(j_neg);
|
||||
CHECK(out_neg.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_neg) == j_neg);
|
||||
@@ -2629,8 +2635,10 @@ TEST_CASE("BJData")
|
||||
// the C++ API stores an int literal as number_integer, so _ArrayType_
|
||||
// names the wire type rather than the storage. Both storages have to
|
||||
// produce the same typed array for every type.
|
||||
// "byte" is checked separately below since it additionally requires
|
||||
// BJData Draft 3 to be selected explicitly (see GitHub issue #5404).
|
||||
for (const char* type :
|
||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char", "byte"
|
||||
{"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64", "char"
|
||||
})
|
||||
{
|
||||
CAPTURE(type);
|
||||
@@ -2641,15 +2649,23 @@ TEST_CASE("BJData")
|
||||
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", type}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}})));
|
||||
}
|
||||
|
||||
{
|
||||
const std::string text = R"({"_ArrayType_":"byte","_ArraySize_":[2,3],"_ArrayData_":[1,2,3,4,5,6]})";
|
||||
const auto from_text = json::to_bjdata(json::parse(text), true, true, json::bjdata_version_t::draft3);
|
||||
CHECK(from_text.at(0) == '[');
|
||||
CHECK(from_text == json::to_bjdata(json({{"_ArrayType_", "byte"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}}),
|
||||
true, true, json::bjdata_version_t::draft3));
|
||||
}
|
||||
|
||||
// negative values under a signed type behave the same way
|
||||
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2],"_ArrayData_":[-5,7]})"));
|
||||
const auto from_neg = json::to_bjdata(json::parse(R"({"_ArrayType_":"int32","_ArraySize_":[2,1],"_ArrayData_":[-5,7]})"));
|
||||
CHECK(from_neg.at(0) == '[');
|
||||
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-5, 7}}})));
|
||||
CHECK(from_neg == json::to_bjdata(json({{"_ArrayType_", "int32"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-5, 7}}})));
|
||||
|
||||
// and so do the floating point types
|
||||
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2],"_ArrayData_":[1.5,2.5]})"));
|
||||
const auto from_float = json::to_bjdata(json::parse(R"({"_ArrayType_":"double","_ArraySize_":[2,1],"_ArrayData_":[1.5,2.5]})"));
|
||||
CHECK(from_float.at(0) == '[');
|
||||
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 2.5}}})));
|
||||
CHECK(from_float == json::to_bjdata(json({{"_ArrayType_", "double"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 2.5}}})));
|
||||
}
|
||||
|
||||
SECTION("optimized ndarray (type and vector-size as 1D array)")
|
||||
@@ -2731,6 +2747,83 @@ TEST_CASE("BJData")
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_size), true, true) == j_size);
|
||||
}
|
||||
|
||||
SECTION("ndarray whose _ArrayType_ is not a string stays as object")
|
||||
{
|
||||
// the type name is looked up as a string below the annotation
|
||||
// check; a non-string _ArrayType_ cannot name a known dtype,
|
||||
// so calling get<string_t>() on it would throw type_error.302
|
||||
// instead of falling back like an unrecognized type name
|
||||
// already does (see GitHub issue #5398)
|
||||
json const j_number = json({{"_ArrayType_", 1}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_number = json::to_bjdata(j_number);
|
||||
CHECK(out_number.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_number) == j_number);
|
||||
|
||||
json const j_null = json({{"_ArrayType_", nullptr}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_null = json::to_bjdata(j_null);
|
||||
CHECK(out_null.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_null) == j_null);
|
||||
|
||||
json const j_bool = json({{"_ArrayType_", true}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_bool = json::to_bjdata(j_bool);
|
||||
CHECK(out_bool.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_bool) == j_bool);
|
||||
|
||||
json const j_array = json({{"_ArrayType_", {"uint8"}}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_array = json::to_bjdata(j_array);
|
||||
CHECK(out_array.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_array) == j_array);
|
||||
|
||||
json const j_object = json({{"_ArrayType_", {{"a", 1}}}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 2}}});
|
||||
const auto out_object = json::to_bjdata(j_object);
|
||||
CHECK(out_object.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_object) == j_object);
|
||||
}
|
||||
|
||||
SECTION("re-serializing a value containing a plain-array-of-bytes is value-stable but not byte-stable")
|
||||
{
|
||||
// OSS-Fuzz found this input (an array whose first element is a
|
||||
// binary_t byte, followed by an object whose _ArrayType_ is
|
||||
// not a string) while exercising the fix for #5398 above: once
|
||||
// the fix stops to_bjdata() from throwing type_error.302 for
|
||||
// the third element, serialization proceeds far enough to
|
||||
// reach a pre-existing, unrelated round-trip quirk in how a
|
||||
// single-byte binary_t value is re-encoded.
|
||||
std::vector<std::uint8_t> const input
|
||||
{
|
||||
0x5b, 0x5b, 0x24, 0x42, 0x23, 0x5b, 0x69, 0x01, 0x5d, 0x5b, 0x5b, 0x5d, 0x7b, 0x55, 0x0b,
|
||||
0x5f, 0x41, 0x72, 0x72, 0x61, 0x79, 0x44, 0x61, 0x74, 0x61, 0x5f, 0x54, 0x55, 0x0b, 0x5f,
|
||||
0x41, 0x72, 0x72, 0x61, 0x79, 0x53, 0x69, 0x7a, 0x65, 0x5f, 0x5a, 0x55, 0x0b, 0x5f, 0x41,
|
||||
0x72, 0x72, 0x61, 0x79, 0x54, 0x79, 0x70, 0x65, 0x5f, 0x54, 0x7d, 0x5d
|
||||
};
|
||||
json const j1 = json::from_bjdata(input);
|
||||
|
||||
// to_bjdata() must not throw (this is what #5398 fixes)
|
||||
std::vector<std::uint8_t> vec2;
|
||||
CHECK_NOTHROW(vec2 = json::to_bjdata(j1, false, false));
|
||||
|
||||
// parsing back a plain (non-optimized) array of bytes cannot
|
||||
// recover that it used to be a binary_t: from_bjdata() has no
|
||||
// way to distinguish "array of uint8 numbers" from "array of
|
||||
// bytes" unless the compact "$U#" array header is used, so
|
||||
// the binary_t collapses into a plain JSON array
|
||||
json const j2 = json::from_bjdata(vec2);
|
||||
CHECK(j1 != j2);
|
||||
CHECK(j2 == json({{91}, json::array(), {{"_ArrayData_", true}, {"_ArraySize_", nullptr}, {"_ArrayType_", true}}}));
|
||||
|
||||
// re-serializing j2 no longer goes through the dedicated
|
||||
// binary_t writer (which always uses the 'U' marker for raw
|
||||
// bytes); the now-plain number 91 goes through the generic
|
||||
// smallest-type writer instead, which - like the rest of the
|
||||
// UBJSON/BJData writer, and unchanged by this fix - prefers
|
||||
// the 'i' (int8) marker over 'U' (uint8) for values that fit
|
||||
// both. Both markers are valid BJData and both decode back to
|
||||
// 91, so this is not byte-for-byte identical to vec2, but it
|
||||
// is value-stable: parsing it again reproduces j2 exactly.
|
||||
std::vector<std::uint8_t> const vec3 = json::to_bjdata(j2, false, false);
|
||||
CHECK(json::from_bjdata(vec3) == j2);
|
||||
}
|
||||
|
||||
SECTION("ndarray whose dimensions overflow stays as object")
|
||||
{
|
||||
// the product of the dimensions wraps around std::size_t to 0
|
||||
@@ -2743,7 +2836,7 @@ TEST_CASE("BJData")
|
||||
// a single dimension that does not fit into std::size_t is
|
||||
// rejected for the same reason (only observable where
|
||||
// std::size_t is narrower than 64 bit)
|
||||
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull}}, {"_ArrayType_", "uint8"}});
|
||||
json j_huge = json({{"_ArrayData_", json::array()}, {"_ArraySize_", {18446744073709551615ull, 2}}, {"_ArrayType_", "uint8"}});
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j_huge), true, true) == j_huge);
|
||||
|
||||
// a well-formed ndarray is still encoded as one
|
||||
@@ -2775,6 +2868,21 @@ TEST_CASE("BJData")
|
||||
const auto out_num = json::to_bjdata(j_num);
|
||||
CHECK(out_num.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_num) == j_num);
|
||||
|
||||
// OSS-Fuzz issue 474400817: an empty object _ArraySize_ was
|
||||
// written as the ND-array header length, which from_bjdata()
|
||||
// could not read back
|
||||
const std::vector<uint8_t> input =
|
||||
{
|
||||
'[', '{', 'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'D', 'a', 't', 'a', '_', 'Z',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'T', 'y', 'p', 'e', '_', 'S', 'i', 5, 'i', 'n', 't', '1', '6',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'S', 'i', 'z', 'e', '_', '{', '}', '}', ']'
|
||||
};
|
||||
const json j1 = json::from_bjdata(input);
|
||||
CHECK(j1 == json::parse(R"([{"_ArrayType_":"int16","_ArraySize_":{},"_ArrayData_":null}])"));
|
||||
json j2;
|
||||
CHECK_NOTHROW(j2 = json::from_bjdata(json::to_bjdata(j1, false, false)));
|
||||
CHECK(j2 == j1);
|
||||
}
|
||||
|
||||
SECTION("ndarray with out-of-range _ArrayData_ elements stays as object")
|
||||
@@ -2786,42 +2894,146 @@ TEST_CASE("BJData")
|
||||
// object encoding that still round-trips (see GitHub issue #5403)
|
||||
|
||||
// an unsigned element that does not fit uint8
|
||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 256}}});
|
||||
json const j_uint8 = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 256}}});
|
||||
const auto out_uint8 = json::to_bjdata(j_uint8);
|
||||
CHECK(out_uint8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint8) == j_uint8);
|
||||
|
||||
// a signed element that does not fit int8
|
||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, 200}}});
|
||||
json const j_int8 = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, 200}}});
|
||||
const auto out_int8 = json::to_bjdata(j_int8);
|
||||
CHECK(out_int8.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_int8) == j_int8);
|
||||
|
||||
// a negative element is likewise out of range for an
|
||||
// unsigned _ArrayType_
|
||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1, -1}}});
|
||||
json const j_uint16_neg = json({{"_ArrayType_", "uint16"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1, -1}}});
|
||||
const auto out_uint16_neg = json::to_bjdata(j_uint16_neg);
|
||||
CHECK(out_uint16_neg.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_uint16_neg) == j_uint16_neg);
|
||||
|
||||
// a double element that overflows to infinity when narrowed
|
||||
// to the "single" (float) precision named by _ArrayType_
|
||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||
json const j_single = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, 1e40}}});
|
||||
const auto out_single = json::to_bjdata(j_single);
|
||||
CHECK(out_single.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_single) == j_single);
|
||||
|
||||
// in-range boundary values still use the compact ndarray encoding
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, ']', 0, 255}));
|
||||
json const j_uint8_ok = json({{"_ArrayType_", "uint8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {0, 255}}});
|
||||
CHECK(json::to_bjdata(j_uint8_ok) == std::vector<uint8_t>({'[', '$', 'U', '#', '[', 'i', 2, 'i', 1, ']', 0, 255}));
|
||||
|
||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2}}, {"_ArrayData_", {-128, 127}}});
|
||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, ']', 0x80, 0x7F}));
|
||||
json const j_int8_ok = json({{"_ArrayType_", "int8"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {-128, 127}}});
|
||||
CHECK(json::to_bjdata(j_int8_ok) == std::vector<uint8_t>({'[', '$', 'i', '#', '[', 'i', 2, 'i', 1, ']', 0x80, 0x7F}));
|
||||
|
||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {1}}, {"_ArrayData_", {1.5}}});
|
||||
json const j_single_ok = json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5, -1.5}}});
|
||||
const auto out_single_ok = json::to_bjdata(j_single_ok);
|
||||
CHECK(out_single_ok.at(0) == '[');
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({1.5f}));
|
||||
CHECK(json::from_bjdata(out_single_ok) == json({{"_ArrayType_", "single"}, {"_ArraySize_", {2, 1}}, {"_ArrayData_", {1.5f, -1.5f}}}));
|
||||
}
|
||||
|
||||
SECTION("ndarray that would not be read back as an annotated object stays as object")
|
||||
{
|
||||
// the reader only restores an annotated object from an ND-array
|
||||
// with at least two non-zero dimensions that is not a 1xN row
|
||||
// vector; any other shape is read back as a plain array. Writing
|
||||
// such an object as an ND-array would drop its annotation, so it
|
||||
// falls back to a plain object encoding that round-trips.
|
||||
for (const char* text :
|
||||
{
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2],"_ArrayData_":[1,2]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[1,2],"_ArrayData_":[1,2]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[0],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,0],"_ArrayData_":[]})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[0,2],"_ArrayData_":[]})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const json j = json::parse(text);
|
||||
for (const bool use_size :
|
||||
{
|
||||
false, true
|
||||
})
|
||||
{
|
||||
const auto out = json::to_bjdata(j, use_size, use_size);
|
||||
CHECK(out.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out) == j);
|
||||
}
|
||||
}
|
||||
|
||||
// a genuine ND-array still uses the compact encoding and round-trips
|
||||
const json j_2d = json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":[1,2]})");
|
||||
const auto out_2d = json::to_bjdata(j_2d);
|
||||
CHECK(out_2d.at(0) == '[');
|
||||
CHECK(json::from_bjdata(out_2d) == j_2d);
|
||||
}
|
||||
|
||||
SECTION("ndarray with non-array _ArrayData_ stays as object")
|
||||
{
|
||||
// the elements are written from _ArrayData_ as a flat list, so it
|
||||
// has to be an array: null has size 0, any other scalar has size 1,
|
||||
// and iterating an object visits its values, so each of these could
|
||||
// match the dimensions and be encoded as an unrelated ND-array
|
||||
for (const char* text :
|
||||
{
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":null})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[2,1],"_ArrayData_":{"a":1,"b":2}})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[1],"_ArrayData_":5})",
|
||||
R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"
|
||||
})
|
||||
{
|
||||
CAPTURE(text);
|
||||
const json j = json::parse(text);
|
||||
const auto out = json::to_bjdata(j);
|
||||
CHECK(out.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out) == j);
|
||||
}
|
||||
|
||||
// OSS-Fuzz issue 563659413: an empty binary _ArraySize_ is written
|
||||
// as a plain object and read back as an empty array, after which
|
||||
// the object with a null _ArrayData_ was encoded as an empty
|
||||
// ND-array and re-read as [], so a second round trip lost the value
|
||||
const std::vector<uint8_t> input =
|
||||
{
|
||||
'{', 'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'D', 'a', 't', 'a', '_', 'Z',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'T', 'y', 'p', 'e', '_', 'S', 'i', 5, 'i', 'n', 't', '1', '6',
|
||||
'U', 11, '_', 'A', 'r', 'r', 'a', 'y', 'S', 'i', 'z', 'e', '_', '[', '$', 'B', '#', '[', ']', '}'
|
||||
};
|
||||
const json j1 = json::from_bjdata(input);
|
||||
const json j2 = json::from_bjdata(json::to_bjdata(j1, false, false));
|
||||
CHECK(j2 == json::parse(R"({"_ArrayType_":"int16","_ArraySize_":[],"_ArrayData_":null})"));
|
||||
CHECK(json::from_bjdata(json::to_bjdata(j2, false, false)) == j2);
|
||||
}
|
||||
|
||||
SECTION("ndarray with _ArrayType_ \"byte\" is gated by the BJData draft version")
|
||||
{
|
||||
// the 'B' (byte) marker used by _ArrayType_ "byte" is only defined
|
||||
// by BJData Draft 3; Draft 2 (the default) has no such marker, so
|
||||
// emitting it unconditionally produced a stream that a Draft 2
|
||||
// reader could not parse as intended (see GitHub issue #5404).
|
||||
// Two dimensions are used so that a successfully written ndarray
|
||||
// round-trips back into the annotated object (a single dimension
|
||||
// is, by the BJData ndarray convention, read back as a plain
|
||||
// binary value rather than the annotated object, same as every
|
||||
// other single-dimension ndarray of a non-"byte" type is read
|
||||
// back as a plain array instead of the annotated object).
|
||||
json const j_byte = json({{"_ArrayType_", "byte"}, {"_ArraySize_", {2, 3}}, {"_ArrayData_", {1, 2, 3, 4, 5, 6}}});
|
||||
|
||||
// default (Draft 2): falls back to a plain object and round-trips
|
||||
const auto out_draft2 = json::to_bjdata(j_byte);
|
||||
CHECK(out_draft2.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_draft2) == j_byte);
|
||||
|
||||
// explicit Draft 2: same as the default
|
||||
const auto out_draft2_explicit = json::to_bjdata(j_byte, true, true, json::bjdata_version_t::draft2);
|
||||
CHECK(out_draft2_explicit.at(0) == '{');
|
||||
CHECK(json::from_bjdata(out_draft2_explicit) == j_byte);
|
||||
|
||||
// Draft 3 explicitly selected: still uses the compact 'B' ndarray encoding
|
||||
const auto out_draft3 = json::to_bjdata(j_byte, true, true, json::bjdata_version_t::draft3);
|
||||
CHECK(out_draft3 == std::vector<uint8_t>({'[', '$', 'B', '#', '[', '$', 'i', '#', 'i', 2, 2, 3, 1, 2, 3, 4, 5, 6}));
|
||||
CHECK(json::from_bjdata(out_draft3) == j_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4077,6 +4289,93 @@ TEST_CASE("BJData use_type requires use_size")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData round-trip invariants")
|
||||
{
|
||||
// This checks what the parse_bjdata_fuzzer driver checks (see
|
||||
// tests/src/fuzzer-parse_bjdata.cpp), so that a regression shows up in CI
|
||||
// rather than as an OSS-Fuzz report: every value from_bjdata() returns
|
||||
// (j1) can be serialized with any combination of options, the result can
|
||||
// be parsed back (j2), and serializing j2 again with the same options
|
||||
// yields a value-equal result.
|
||||
//
|
||||
// Beyond the driver, this also checks that j2 equals j1 and that
|
||||
// serializing j2 reproduces the exact bytes, both except for values that
|
||||
// contain a binary value: a binary value is only written as a binary
|
||||
// value with Draft 3's optimized binary array, and otherwise read back as
|
||||
// an array of integers, for which the writer may choose different (but
|
||||
// equally valid) type markers when it is serialized again (see #5494).
|
||||
//
|
||||
// Values are compared with dump() rather than operator==, because a NaN
|
||||
// never compares equal to itself.
|
||||
struct options
|
||||
{
|
||||
bool use_size;
|
||||
bool use_type;
|
||||
json::bjdata_version_t version;
|
||||
};
|
||||
const std::vector<options> all_options =
|
||||
{
|
||||
{false, false, json::bjdata_version_t::draft2},
|
||||
{true, false, json::bjdata_version_t::draft2},
|
||||
{true, true, json::bjdata_version_t::draft2},
|
||||
{false, false, json::bjdata_version_t::draft3},
|
||||
{true, false, json::bjdata_version_t::draft3},
|
||||
{true, true, json::bjdata_version_t::draft3},
|
||||
};
|
||||
|
||||
for (const auto& j0 : utils::round_trip_corpus::values())
|
||||
{
|
||||
// turn the corpus value into a value as from_bjdata() returns it
|
||||
for (const auto& initial : all_options)
|
||||
{
|
||||
const json j1 = json::from_bjdata(json::to_bjdata(j0, initial.use_size, initial.use_type, initial.version));
|
||||
const bool has_binary = utils::round_trip_corpus::contains_binary(j1);
|
||||
|
||||
for (const auto& o : all_options)
|
||||
{
|
||||
INFO("j1 = " << j1.dump() << ", use_size = " << o.use_size << ", use_type = " << o.use_type
|
||||
<< ", draft3 = " << (o.version == json::bjdata_version_t::draft3));
|
||||
|
||||
const std::vector<std::uint8_t> vec = json::to_bjdata(j1, o.use_size, o.use_type, o.version);
|
||||
json j2;
|
||||
// anything the library writes must be parsable by the library
|
||||
REQUIRE_NOTHROW(j2 = json::from_bjdata(vec));
|
||||
const std::vector<std::uint8_t> vec2 = json::to_bjdata(j2, o.use_size, o.use_type, o.version);
|
||||
CHECK(json::from_bjdata(vec2).dump() == j2.dump());
|
||||
|
||||
if (!has_binary)
|
||||
{
|
||||
CHECK(j2.dump() == j1.dump());
|
||||
CHECK(vec2 == vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("BJData round trip of a binary value is value-stable, not byte-stable")
|
||||
{
|
||||
// OSS-Fuzz issue 474480402: a Draft 3 optimized binary array is read as a
|
||||
// binary value, which to_bjdata() writes in the default Draft 2 mode as a
|
||||
// plain array of uint8 numbers. That is read back as an array of numbers,
|
||||
// for which the writer then picks the smallest type marker, int8 ('i'),
|
||||
// so re-serializing changes the bytes, but not the value. This is the
|
||||
// exception described in the "Round trips" note of the BJData
|
||||
// documentation, and why the fuzzer checks value stability (see #5494).
|
||||
const std::vector<uint8_t> input = {'[', '$', 'B', '#', 'U', 1, 0x20};
|
||||
const json j1 = json::from_bjdata(input);
|
||||
CHECK(j1 == json::binary({0x20}));
|
||||
|
||||
const std::vector<uint8_t> vec = json::to_bjdata(j1, false, false);
|
||||
CHECK(vec == std::vector<uint8_t>({'[', 'U', 0x20, ']'}));
|
||||
const json j2 = json::from_bjdata(vec);
|
||||
CHECK(j2 == json::array({0x20}));
|
||||
|
||||
const std::vector<uint8_t> vec2 = json::to_bjdata(j2, false, false);
|
||||
CHECK(vec2 == std::vector<uint8_t>({'[', 'i', 0x20, ']'}));
|
||||
CHECK(json::from_bjdata(vec2) == j2);
|
||||
}
|
||||
|
||||
TEST_CASE("BJData roundtrips" * doctest::skip())
|
||||
{
|
||||
SECTION("input from self-generated BJData files")
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
// __ _____ _____ _____
|
||||
// __| | __| | | | JSON for Modern C++ (supporting code)
|
||||
// | | |__ | | | | | | version 3.12.0
|
||||
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "doctest_compatibility.h"
|
||||
|
||||
// This file tests the opt-in JSON_BRACE_INIT_COPY_SEMANTICS, so it defines the
|
||||
// macro itself rather than relying on a -D flag, and runs in every build.
|
||||
#ifdef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||
#endif
|
||||
|
||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 1
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
using nlohmann::json;
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define STRINGIZE_EX(x) #x
|
||||
#define STRINGIZE(x) STRINGIZE_EX(x)
|
||||
|
||||
TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS")
|
||||
{
|
||||
SECTION("the macro is part of the ABI tag")
|
||||
{
|
||||
const std::string ns = STRINGIZE(NLOHMANN_JSON_NAMESPACE);
|
||||
// other tags may come before it, e.g. json_abi_ldvcmp_bics
|
||||
CHECK(ns.find("_bics") != std::string::npos);
|
||||
}
|
||||
|
||||
SECTION("single-element brace initialization copies the element (#5074)")
|
||||
{
|
||||
json const j_obj = {{"key", "value"}, {"num", 42}};
|
||||
json const j_arr = {1, 2, 3};
|
||||
|
||||
// object: brace init copies instead of wrapping
|
||||
json const j1{j_obj};
|
||||
CHECK(j1.is_object());
|
||||
CHECK(j1 == j_obj);
|
||||
|
||||
// array: brace init copies instead of wrapping
|
||||
json const j2{j_arr};
|
||||
CHECK(j2.is_array());
|
||||
CHECK(j2.size() == 3);
|
||||
CHECK(j2 == j_arr);
|
||||
|
||||
// this applies to any single element, not only to JSON values
|
||||
json const j3{true};
|
||||
CHECK(j3.is_boolean());
|
||||
|
||||
json const j4{42};
|
||||
CHECK(j4.is_number_integer());
|
||||
|
||||
json const j5 = {1};
|
||||
CHECK(j5 == 1);
|
||||
|
||||
json const j6 = {"text"};
|
||||
CHECK(j6 == "text");
|
||||
|
||||
json const j7 = {{1, 2}};
|
||||
CHECK(j7 == json::array({1, 2}));
|
||||
}
|
||||
|
||||
SECTION("what the macro does not change")
|
||||
{
|
||||
// lists with more than one element are unaffected
|
||||
json const j1 = {1, 2};
|
||||
CHECK(j1.is_array());
|
||||
CHECK(j1.size() == 2);
|
||||
|
||||
// a single [string, value] pair still describes an object
|
||||
json const j2 = {{"key", "value"}};
|
||||
CHECK(j2.is_object());
|
||||
CHECK(j2["key"] == "value");
|
||||
|
||||
// json::array() always creates an array
|
||||
json const j3 = json::array({1});
|
||||
CHECK(j3.is_array());
|
||||
CHECK(j3.size() == 1);
|
||||
CHECK(j3[0] == 1);
|
||||
|
||||
json const j_obj = {{"key", "value"}};
|
||||
json const j4 = json::array({j_obj});
|
||||
CHECK(j4.is_array());
|
||||
CHECK(j4.size() == 1);
|
||||
CHECK(j4[0] == j_obj);
|
||||
}
|
||||
|
||||
SECTION("conversions build the same values as without the macro")
|
||||
{
|
||||
SECTION("one-element std::tuple")
|
||||
{
|
||||
json const j1 = std::tuple<int> {5};
|
||||
CHECK(j1.dump() == "[5]");
|
||||
CHECK(std::get<0>(j1.get<std::tuple<int>>()) == 5);
|
||||
|
||||
json const j2 = std::tuple<std::string> {"text"};
|
||||
CHECK(j2.dump() == "[\"text\"]");
|
||||
CHECK(std::get<0>(j2.get<std::tuple<std::string>>()) == "text");
|
||||
|
||||
json const j3 = std::tuple<json> {json::array({1, 2})};
|
||||
CHECK(j3.dump() == "[[1,2]]");
|
||||
|
||||
// as without the macro, a [string, value] pair becomes an object
|
||||
// member (see the known limitation documented for std::pair)
|
||||
json const j4 = std::tuple<std::pair<std::string, int>> {{"a", 1}};
|
||||
CHECK(j4.dump() == "{\"a\":1}");
|
||||
}
|
||||
|
||||
SECTION("tuples with more elements")
|
||||
{
|
||||
json const j1 = std::tuple<int, std::string> {1, "a"};
|
||||
CHECK(j1.dump() == "[1,\"a\"]");
|
||||
|
||||
json const j2 = std::tuple<> {};
|
||||
CHECK(j2.dump() == "[]");
|
||||
}
|
||||
|
||||
SECTION("one-element containers")
|
||||
{
|
||||
json const j1 = std::vector<int> {1};
|
||||
CHECK(j1.dump() == "[1]");
|
||||
CHECK(j1.get<std::vector<int>>() == std::vector<int> {1});
|
||||
|
||||
std::array<int, 1> const arr = {{1}};
|
||||
json const j2 = arr;
|
||||
CHECK(j2.dump() == "[1]");
|
||||
|
||||
json const j3 = std::list<std::string> {"a"};
|
||||
CHECK(j3.dump() == "[\"a\"]");
|
||||
|
||||
json const j4 = std::map<std::string, int> {{"a", 1}};
|
||||
CHECK(j4.dump() == "{\"a\":1}");
|
||||
|
||||
json const j5 = std::map<int, int> {{1, 2}};
|
||||
CHECK(j5.dump() == "[[1,2]]");
|
||||
}
|
||||
|
||||
SECTION("std::pair")
|
||||
{
|
||||
json const j = std::pair<int, int> {1, 2};
|
||||
CHECK(j.dump() == "[1,2]");
|
||||
CHECK((j.get<std::pair<int, int>>() == std::pair<int, int> {1, 2}));
|
||||
}
|
||||
|
||||
SECTION("items()")
|
||||
{
|
||||
json j_obj = {{"key", 1}};
|
||||
for (const auto& el : j_obj.items())
|
||||
{
|
||||
json const j = el;
|
||||
CHECK(j.dump() == "{\"key\":1}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user