mirror of
https://github.com/nlohmann/json.git
synced 2026-09-26 09:50:30 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c637e73bea | ||
|
|
f422b753cc | ||
|
|
95e9a5931c | ||
|
|
1e44262091 | ||
|
|
c60a0bc336 | ||
|
|
d19f7f5dce | ||
|
|
632a5812a8 | ||
|
|
ec4bdc398a | ||
|
|
a9ab2a62ba | ||
|
|
ad2c14b985 | ||
|
|
e1310ad43c | ||
|
|
465407f3ce | ||
|
|
02dd3e67f2 | ||
|
|
abbe52d6de | ||
|
|
98278dc3f6 | ||
|
|
6c8ea0a6d1 | ||
|
|
01b53c8c15 | ||
|
|
cc472af13f | ||
|
|
80bf54a5a2 | ||
|
|
aada27405d | ||
|
|
3901b223e5 | ||
|
|
f290b36ad2 | ||
|
|
4daca40d7b | ||
|
|
7c90ec2323 |
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
- [ ] The changes are described in detail, both the what and why.
|
- [ ] 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, 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.
|
- [ ] 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.
|
- [ ] If applicable, the [documentation](https://json.nlohmann.me) is updated.
|
||||||
- [ ] The source code is amalgamated by running `make amalgamate`.
|
- [ ] 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
|
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.
|
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
|
We will send a first response within 14 days, indicating the next steps in handling your report. After the initial
|
||||||
will keep you informed of the progress towards a fix and full announcement and may ask for additional information or
|
reply to your report, we will keep you informed of the progress towards a fix and full announcement and may ask for
|
||||||
guidance.
|
additional information or guidance.
|
||||||
|
|
||||||
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
For vulnerabilities in third-party dependencies or modules, please report them directly to the respective maintainers.
|
||||||
|
|
||||||
|
## 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
|
## Unofficial packages
|
||||||
|
|
||||||
This project does not publish an official npm package. The npm package
|
This project does not publish an official npm package. The npm package
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ name: "Check amalgamation"
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
name: CIFuzz
|
name: CIFuzz
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
with:
|
with:
|
||||||
languages: c-cpp
|
languages: c-cpp
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/autobuild@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
name: 'Dependency Review'
|
name: 'Dependency Review'
|
||||||
on: [pull_request]
|
on: [pull_request]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
name: flawfinder
|
name: flawfinder
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@@ -43,6 +47,6 @@ jobs:
|
|||||||
output: 'flawfinder_results.sarif'
|
output: 'flawfinder_results.sarif'
|
||||||
|
|
||||||
- name: Upload analysis results to GitHub Security tab
|
- name: Upload analysis results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
with:
|
with:
|
||||||
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
sarif_file: ${{github.workspace}}/flawfinder_results.sarif
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ on:
|
|||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize]
|
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:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: ["develop"]
|
branches: ["develop"]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@@ -76,6 +80,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '23 2 * * 4'
|
- cron: '23 2 * * 4'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
@@ -61,7 +65,7 @@ jobs:
|
|||||||
|
|
||||||
# Upload SARIF file generated in previous step
|
# Upload SARIF file generated in previous step
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
|
uses: github/codeql-action/upload-sarif@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
|
||||||
with:
|
with:
|
||||||
sarif_file: semgrep.sarif
|
sarif_file: semgrep.sarif
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -124,11 +124,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||||
- name: Run CMake (Release)
|
- name: Run CMake (Release)
|
||||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
|
||||||
if: matrix.build_type == 'Release'
|
if: matrix.build_type == 'Release'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Run CMake (Debug)
|
- 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'
|
if: matrix.build_type == 'Debug'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ cc_library(
|
|||||||
],
|
],
|
||||||
includes = ["include"],
|
includes = ["include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
alwayslink = True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
[](https://github.com/nlohmann/json/releases)
|
[](https://github.com/nlohmann/json/releases)
|
||||||
[](https://github.com/nlohmann/json/issues)
|
[](https://github.com/nlohmann/json/issues)
|
||||||
[](https://isitmaintained.com/project/nlohmann/json "Average time to resolve an issue")
|
[](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://scorecard.dev/viewer/?uri=github.com/nlohmann/json)
|
||||||
[](https://cloudback.it)
|
[](https://cloudback.it)
|
||||||
[](https://github.com/sponsors/nlohmann)
|
[](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/).
|
- **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:
|
Other aspects were not so important to us:
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -300,10 +300,10 @@ add_custom_target(ci_test_skiplibraryversioncheck
|
|||||||
# Disable thread-local storage.
|
# Disable thread-local storage.
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
# Without thread-local storage, the copy constructor cannot bound its descent
|
# Without thread-local storage, copying and comparing cannot bound their
|
||||||
# and copies every object and array without the call stack. That path is
|
# descent and handle every object and array without the call stack. Those paths
|
||||||
# otherwise only reached by values nested deeper than the bound, so this target
|
# are otherwise only reached by values nested deeper than the bound, so this
|
||||||
# is what runs the whole test suite through it.
|
# target is what runs the whole test suite through them.
|
||||||
add_custom_target(ci_test_no_thread_local
|
add_custom_target(ci_test_no_thread_local
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
-DCMAKE_BUILD_TYPE=Debug -GNinja
|
||||||
@@ -619,7 +619,7 @@ add_custom_target(ci_single_binaries
|
|||||||
add_custom_target(ci_benchmarks
|
add_custom_target(ci_benchmarks
|
||||||
COMMAND ${CMAKE_COMMAND}
|
COMMAND ${CMAKE_COMMAND}
|
||||||
-DCMAKE_BUILD_TYPE=Release -GNinja
|
-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 ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_benchmarks --target json_benchmarks
|
||||||
COMMAND cd ${PROJECT_BINARY_DIR}/build_benchmarks && ./json_benchmarks
|
COMMAND cd ${PROJECT_BINARY_DIR}/build_benchmarks && ./json_benchmarks
|
||||||
COMMENT "Run benchmarks"
|
COMMENT "Run benchmarks"
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ if(CMAKE_CROSSCOMPILING)
|
|||||||
endif()
|
endif()
|
||||||
if(NOT DEFINED LIBCPP_VERSION_OUTPUT_CACHED)
|
if(NOT DEFINED LIBCPP_VERSION_OUTPUT_CACHED)
|
||||||
try_run(RUN_RESULT_VAR COMPILE_RESULT_VAR
|
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
|
RUN_OUTPUT_VARIABLE LIBCPP_VERSION_OUTPUT
|
||||||
COMPILE_OUTPUT_VARIABLE LIBCPP_VERSION_COMPILE_OUTPUT
|
COMPILE_OUTPUT_VARIABLE LIBCPP_VERSION_COMPILE_OUTPUT
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ string(APPEND CONTENT [=[
|
|||||||
],
|
],
|
||||||
includes = ["include"],
|
includes = ["include"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
alwayslink = True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cc_library(
|
cc_library(
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ This overload is chosen if:
|
|||||||
- `ValueType` is not `basic_json`,
|
- `ValueType` is not `basic_json`,
|
||||||
- `json_serializer<ValueType>` has a `from_json()` method of the form `void from_json(const basic_json&, ValueType&)`
|
- `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
|
## Template parameters
|
||||||
|
|
||||||
`ValueType`
|
`ValueType`
|
||||||
@@ -67,3 +71,4 @@ Depends on the `json_serializer<ValueType>::from_json()` implementation.
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Since version 3.3.0.
|
- Since version 3.3.0.
|
||||||
|
- Added a `static_assert` with a clear message for `const` arguments in version 3.13.0.
|
||||||
|
|||||||
@@ -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
|
[`input_format_t`](input_format_t.md) for more information
|
||||||
|
|
||||||
`strict` (in)
|
`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)
|
`ignore_comments` (in)
|
||||||
: whether comments should be ignored and treated like whitespace (`#!cpp true`) or yield a parse error
|
: 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.
|
- Added `ignore_trailing_commas` in version 3.13.0.
|
||||||
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
- Extended container support (1) to include types with lvalue-only ADL `begin`/`end` (matching `std::begin`/`std::end` semantics) in version 3.13.0.
|
||||||
- Extended overload (2) to accept heterogeneous iterator+sentinel pairs (C++20 ranges support) in version 3.13.0.
|
- 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"
|
!!! warning "Deprecation"
|
||||||
|
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
|
|||||||
|
|
||||||
## Complexity
|
## Complexity
|
||||||
|
|
||||||
Proportional to the size of the JSON value `j` multiplied by its maximum nesting
|
Linear in the size of the JSON value `j`. The length prefixes of all nested documents and arrays are computed in one
|
||||||
depth, `O(n × d)`. BSON length prefixes are computed recursively before nested
|
pass before anything is written.
|
||||||
values are written.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -77,3 +76,4 @@ values are written.
|
|||||||
## Version history
|
## Version history
|
||||||
|
|
||||||
- Added in version 3.4.0.
|
- 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.
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ header. See also the [macro overview page](../../features/macros.md).
|
|||||||
|
|
||||||
## Parsing
|
## 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
|
- [**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
|
treating it as end of input
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@
|
|||||||
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
|
When defined, the library does not use `#!cpp thread_local` storage. This is relevant for the few environments whose
|
||||||
toolchain does not support it.
|
toolchain does not support it.
|
||||||
|
|
||||||
The copy constructor copies the first levels of a value by copying the containers, which copy their elements, and
|
Copying a value and comparing two values both descend into the first levels by letting the containers copy or compare
|
||||||
completes whatever is nested deeper than that without the call stack, so that copying a value cannot exhaust the stack
|
themselves, and finish whatever is nested deeper than that without the call stack, so that neither can exhaust the stack
|
||||||
however deeply it is nested. It counts the levels it has descended into in a `#!cpp thread_local` variable, as a counter
|
however deeply the values are nested. Each counts the levels it has descended into in a `#!cpp thread_local` variable, as
|
||||||
shared between threads would be raced.
|
a counter shared between threads would be raced.
|
||||||
|
|
||||||
Without that counter, no descent can be bounded safely, so objects and arrays are copied without the call stack right
|
Without those counters, no descent can be bounded safely, so objects and arrays are copied and compared without the call
|
||||||
away. Copying keeps working exactly as it does otherwise - the same values come out, and deeply nested values are copied
|
stack right away. Both keep working exactly as they do otherwise - the same values come out, the same comparisons hold,
|
||||||
just as safely - but copying is slower, because the containers no longer copy themselves. Copying the benchmark
|
and deeply nested values are handled just as safely - but both are slower, because the containers no longer copy or
|
||||||
documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer; values built mostly from objects are affected the
|
compare themselves. Copying the benchmark documents takes 9% (`canada.json`) to 34% (`twitter.json`) longer, and
|
||||||
most.
|
comparing two equal ones 10% (`citm_catalog.json`) to 90% (`canada.json`) longer.
|
||||||
|
|
||||||
## Default definition
|
## Default definition
|
||||||
|
|
||||||
@@ -28,6 +28,7 @@ By default, `#!cpp JSON_NO_THREAD_LOCAL` is not defined.
|
|||||||
|
|
||||||
The library defines it by itself for Clang targeting MinGW, which does not survive the `#!cpp thread_local` storage:
|
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 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
|
## Examples
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -65,6 +65,12 @@ The default value is `0` (disabled — existing behavior is preserved).
|
|||||||
for CBOR or MessagePack, are never affected by this trimming; their full extent - including a genuine trailing
|
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.
|
`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"
|
!!! tip "Workaround without the macro"
|
||||||
|
|
||||||
To reject a NUL byte without enabling this macro, trim your input yourself before calling `parse()`:
|
To reject a NUL byte without enabling this macro, trim your input yourself before calling `parse()`:
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ Summary:
|
|||||||
: name of the base type (class, struct) `type` is derived from
|
: name of the base type (class, struct) `type` is derived from
|
||||||
|
|
||||||
`member` (in)
|
`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
|
## 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).
|
- 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.
|
- 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"
|
!!! warning "Implementation limits"
|
||||||
|
|
||||||
See Implementation limits for [NLOHMANN_DEFINE_TYPE_INTRUSIVE](nlohmann_define_type_intrusive.md) and
|
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
|
: name of the type (class, struct) to serialize/deserialize
|
||||||
|
|
||||||
`member` (in)
|
`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
|
## 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
|
[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"
|
!!! warning "Implementation limits"
|
||||||
|
|
||||||
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
|
- 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
|
: name of the type (class, struct) to serialize/deserialize
|
||||||
|
|
||||||
`member` (in)
|
`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
|
## 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
|
[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"
|
!!! warning "Implementation limits"
|
||||||
|
|
||||||
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
|
- The current implementation is limited to at most 63 member variables. If you want to serialize/deserialize types
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ input >> j2; // parses the next value
|
|||||||
Only numbers are affected. Values ending in a self-delimiting character do not read past themselves, so
|
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.
|
`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)
|
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.
|
(newline-delimited JSON) input -- see that page for why and for the recommended alternative.
|
||||||
@@ -107,9 +109,12 @@ being read.
|
|||||||
- [parse](basic_json/parse.md) - deserialize from a compatible input
|
- [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
|
- [`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
|
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
|
## Version history
|
||||||
|
|
||||||
- Added in version 1.0.0.
|
- 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
|
- `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.
|
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
|
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.
|
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
|
## Support
|
||||||
|
|
||||||
All participants in the community are encouraged to provide support for new users within the project management
|
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
|
- [Contribution Guidelines](contribution_guidelines.md) - guidelines how to contribute to this project
|
||||||
- [Governance](governance.md) - the governance model of this project
|
- [Governance](governance.md) - the governance model of this project
|
||||||
- [Quality Assurance](quality_assurance.md) - how the quality of this project is assured
|
- [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
|
- [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] 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
|
- [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.
|
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
|
## 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
|
- [x] The test suite is executed with [Sanitizers](https://github.com/google/sanitizers) (address sanitizer, undefined
|
||||||
behavior sanitizer, integer overflow detection, nullability violations).
|
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
|
## Style check
|
||||||
|
|
||||||
!!! success "Requirement: Common code style"
|
!!! 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
|
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"
|
!!! note "No macro for non-default-constructible types"
|
||||||
|
|
||||||
There is currently no `NLOHMANN_DEFINE_TYPE_*`-style macro for types that are not
|
There is currently no `NLOHMANN_DEFINE_TYPE_*`-style macro for types that are not
|
||||||
|
|||||||
@@ -208,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.
|
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
|
??? example
|
||||||
|
|
||||||
```cpp
|
```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
|
If BSON input must be validated for strict specification compliance, validate it separately before passing it to
|
||||||
`from_bson()`.
|
`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
|
??? example
|
||||||
|
|
||||||
```cpp
|
```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.
|
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"
|
!!! 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.
|
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`.
|
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
|
??? example
|
||||||
|
|
||||||
|
|||||||
@@ -93,11 +93,21 @@ See [full documentation of `JSON_NO_IO`](../api/macros/json_no_io.md).
|
|||||||
|
|
||||||
## `JSON_NO_THREAD_LOCAL`
|
## `JSON_NO_THREAD_LOCAL`
|
||||||
|
|
||||||
When defined, the library does not use `#!cpp thread_local` storage. Copying a value then always avoids the call stack
|
When defined, the library does not use `#!cpp thread_local` storage. Copying a value and comparing two values then
|
||||||
rather than descending into a bounded number of levels first, which is slower but yields the same values.
|
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).
|
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`
|
## `JSON_SKIP_LIBRARY_VERSION_CHECK`
|
||||||
|
|
||||||
When defined, the library will not create a compiler warning when a different version of the library was already
|
When defined, the library will not create a compiler warning when a different version of the library was already
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ The complete default namespace name is derived as follows:
|
|||||||
- [`JSON_DIAGNOSTIC_POSITIONS`](../api/macros/json_diagnostic_positions.md) defined non-zero appends `_dp`.
|
- [`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
|
- [`JSON_BRACE_INIT_COPY_SEMANTICS`](../api/macros/json_brace_init_copy_semantics.md) defined non-zero appends
|
||||||
`_bics`.
|
`_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
|
- 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)
|
underscores. To omit the version component, see [Disabling the version component](#disabling-the-version-component)
|
||||||
below.
|
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`.
|
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
|
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.
|
[`operator>>` notes](../../api/operator_gtgt.md#notes) for details and examples.
|
||||||
|
|
||||||
## SAX vs. DOM parsing
|
## SAX vs. DOM parsing
|
||||||
|
|||||||
@@ -562,7 +562,11 @@ binary32 or binary64 field and have no encoding for `#!cpp long double`.
|
|||||||
## `AllocatorType`
|
## `AllocatorType`
|
||||||
|
|
||||||
`AllocatorType` is instantiated with **one** argument, for each of `object_t`, `array_t`, `string_t`, `binary_t`,
|
`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
|
### Always required
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,125 @@
|
|||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
!!! info
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
## Overview
|
## 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
|
```mermaid
|
||||||
- container interface
|
flowchart LR
|
||||||
- iterators
|
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 text** is read by an [input adapter](#input-adapters), tokenized by the lexer, and turned into SAX events by
|
||||||
- [`json`](../api/json.md)
|
the parser.
|
||||||
- [`ordered_json`](../api/ordered_json.md) via [`ordered_map`](../api/ordered_map.md)
|
- **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
|
## 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
|
```cpp
|
||||||
/// the type of the current element
|
struct data
|
||||||
value_t m_type = value_t::null;
|
{
|
||||||
|
/// the type of the current element
|
||||||
|
value_t m_type = value_t::null;
|
||||||
|
|
||||||
/// the value of the current element
|
/// the value of the current element
|
||||||
json_value m_value = {};
|
json_value m_value = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
data m_data = {};
|
||||||
```
|
```
|
||||||
|
|
||||||
with
|
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
|
```cpp
|
||||||
/// read a single character
|
/// the type of the characters in the input
|
||||||
std::char_traits<char>::int_type get_character() noexcept;
|
using char_type = ...;
|
||||||
|
|
||||||
/// read multiple characters to a destination buffer and
|
/// read a single character; returns std::char_traits<char_type>::eof() at the end of the input
|
||||||
/// returns the number of characters successfully read
|
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>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1);
|
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**:
|
Output is written via **output adapters**:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
template<typename T>
|
|
||||||
void write_character(CharType c);
|
void write_character(CharType c);
|
||||||
|
|
||||||
template<typename CharType>
|
|
||||||
void write_characters(const CharType* s, std::size_t length);
|
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
|
## 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
|
```cpp
|
||||||
template<class T>
|
template<class T>
|
||||||
void to_json(basic_json& j, const T& 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);
|
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
|
## Additional features
|
||||||
|
|
||||||
- JSON Pointers
|
- [JSON Pointer](../features/json_pointer.md) (class `json_pointer`) addresses values inside a tree. It is also the
|
||||||
- Binary formats
|
basis of [JSON Patch](../features/json_patch.md).
|
||||||
- Custom base class
|
- [Binary formats](../features/binary_formats/index.md) are read by `binary_reader` and written by `binary_writer`.
|
||||||
- Conversion macros
|
- 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
|
## 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.
|
- **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:
|
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
|
### 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
|
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"
|
!!! 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 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
|
### json.exception.parse_error.114
|
||||||
|
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ nav:
|
|||||||
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
- 'JSON_NOEXCEPTION': api/macros/json_noexception.md
|
||||||
- 'JSON_NO_IO': api/macros/json_no_io.md
|
- 'JSON_NO_IO': api/macros/json_no_io.md
|
||||||
- 'JSON_NO_THREAD_LOCAL': api/macros/json_no_thread_local.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_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_SKIP_UNSUPPORTED_COMPILER_CHECK': api/macros/json_skip_unsupported_compiler_check.md
|
||||||
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
|
- 'JSON_STRICT_NUL_HANDLING': api/macros/json_strict_nul_handling.md
|
||||||
@@ -317,7 +318,9 @@ nav:
|
|||||||
- community/contribution_guidelines.md
|
- community/contribution_guidelines.md
|
||||||
- community/quality_assurance.md
|
- community/quality_assurance.md
|
||||||
- community/governance.md
|
- community/governance.md
|
||||||
|
- community/roadmap.md
|
||||||
- community/security_policy.md
|
- community/security_policy.md
|
||||||
|
- community/assurance_case.md
|
||||||
|
|
||||||
# Extras
|
# Extras
|
||||||
extra:
|
extra:
|
||||||
|
|||||||
@@ -38,6 +38,14 @@
|
|||||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||||
#endif
|
#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
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -62,21 +70,35 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||||
#endif
|
#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
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
|
#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) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
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_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <nlohmann/detail/meta/is_sax.hpp>
|
#include <nlohmann/detail/meta/is_sax.hpp>
|
||||||
#include <nlohmann/detail/meta/type_traits.hpp>
|
#include <nlohmann/detail/meta/type_traits.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.hpp>
|
#include <nlohmann/detail/value_t.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
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));
|
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 //
|
// CBOR //
|
||||||
//////////
|
//////////
|
||||||
@@ -3304,7 +3317,28 @@ class binary_reader
|
|||||||
const NumberType len,
|
const NumberType len,
|
||||||
string_t& result)
|
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
|
// maintain ifstream flags, except eof
|
||||||
if (is != nullptr)
|
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);
|
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;
|
||||||
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
|
input_stream_adapter(input_stream_adapter&& rhs) noexcept
|
||||||
: is(rhs.is), sb(rhs.sb)
|
: 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
|
// 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
|
// ensure that std::char_traits<char>::eof() and the character 0xFF do not
|
||||||
// end up as the same value, e.g., 0xFFFFFFFF.
|
// 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()
|
std::char_traits<char>::int_type get_character()
|
||||||
{
|
{
|
||||||
auto res = sb->sbumpc();
|
auto res = sb->sbumpc();
|
||||||
@@ -134,10 +194,14 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::size_t get_elements(T* dest, std::size_t count = 1)
|
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))));
|
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)))
|
if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))
|
||||||
{
|
{
|
||||||
@@ -147,9 +211,27 @@ class input_stream_adapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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
|
/// the associated input stream
|
||||||
std::istream* is = nullptr;
|
std::istream* is = nullptr;
|
||||||
std::streambuf* sb = 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
|
#endif // JSON_NO_IO
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,25 @@ constexpr bool input_adapter_supports_seek(std::false_type /*detected*/)
|
|||||||
return false;
|
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
|
// Detect whether an input adapter exposes a contiguous byte block that the
|
||||||
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
// lexer can scan directly (see iterator_input_adapter::supports_bulk_scan).
|
||||||
// Adapters without the flag - file, stream, wide-string, user-defined - fall
|
// 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 =
|
static constexpr bool lazy_token_string =
|
||||||
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
input_adapter_supports_seek<InputAdapterType>(is_detected<detect_supports_seek, InputAdapterType> {});
|
||||||
|
|
||||||
|
/// whether 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
|
/// whether string scanning may bulk-consume runs of ordinary characters
|
||||||
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
/// directly from a contiguous input buffer (SWAR fast path). This requires
|
||||||
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
/// the token to be reconstructible lazily (lazy_token_string), so bypassing
|
||||||
@@ -1898,6 +1923,21 @@ scan_number_done:
|
|||||||
uncapture_char(std::integral_constant<bool, lazy_token_string> {});
|
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
|
/// seekable adapter: nothing was captured, so nothing to undo
|
||||||
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
void uncapture_char(std::true_type /*lazy*/) const noexcept {}
|
||||||
|
|
||||||
@@ -1961,6 +2001,31 @@ scan_number_done:
|
|||||||
return position;
|
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
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
/// return the offset of the first character of the last read token; unlike
|
/// return the offset of the first character of the last read token; unlike
|
||||||
/// the token's parsed value, this accounts for escape sequences
|
/// the token's parsed value, this accounts for escape sequences
|
||||||
|
|||||||
@@ -100,13 +100,22 @@ class parser
|
|||||||
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
// in strict mode, input must be completely read
|
if (strict)
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
// in strict mode, input must be completely read
|
||||||
m_lexer.get_token_string(),
|
if (get_token() != token_type::end_of_input)
|
||||||
parse_error::create(101, m_lexer.get_position(),
|
{
|
||||||
exception_message(token_type::end_of_input, "value"), nullptr));
|
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
|
// 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);
|
json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);
|
||||||
sax_parse_internal(&sdp);
|
sax_parse_internal(&sdp);
|
||||||
|
|
||||||
// in strict mode, input must be completely read
|
if (strict)
|
||||||
if (strict && (get_token() != token_type::end_of_input))
|
|
||||||
{
|
{
|
||||||
sdp.parse_error(m_lexer.get_position(),
|
// in strict mode, input must be completely read
|
||||||
m_lexer.get_token_string(),
|
if (get_token() != token_type::end_of_input)
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
{
|
||||||
|
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
|
// in case of an error, return a discarded value
|
||||||
@@ -166,12 +183,24 @@ class parser
|
|||||||
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
(void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};
|
||||||
const bool result = sax_parse_internal(sax);
|
const bool result = sax_parse_internal(sax);
|
||||||
|
|
||||||
// strict mode: next byte must be EOF
|
if (result)
|
||||||
if (result && strict && (get_token() != token_type::end_of_input))
|
|
||||||
{
|
{
|
||||||
return sax->parse_error(m_lexer.get_position(),
|
if (strict)
|
||||||
m_lexer.get_token_string(),
|
{
|
||||||
parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, "value"), nullptr));
|
// 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;
|
return result;
|
||||||
|
|||||||
@@ -596,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__
|
#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
|
@brief macro
|
||||||
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
|
@def NLOHMANN_DEFINE_TYPE_INTRUSIVE
|
||||||
@since version 3.9.0
|
@since version 3.9.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -616,10 +662,15 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.11.0
|
@since version 3.11.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -630,9 +681,14 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.11.3
|
@since version 3.11.3
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/
|
@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__)) })
|
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, ...) \
|
#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__)) })
|
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__)) })
|
||||||
|
|
||||||
@@ -642,10 +698,17 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.9.0
|
@since version 3.9.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -656,10 +719,15 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.11.0
|
@since version 3.11.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -670,9 +738,14 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.11.3
|
@since version 3.11.3
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/
|
@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__)) })
|
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, ...) \
|
#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__)) })
|
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__)) })
|
||||||
|
|
||||||
@@ -682,10 +755,17 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -696,10 +776,15 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -710,9 +795,14 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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__)) })
|
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, ...) \
|
#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__)) })
|
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__)) })
|
||||||
|
|
||||||
@@ -723,10 +813,17 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -737,10 +834,15 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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 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__)) })
|
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, ...) \
|
#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 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__)) })
|
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__)) })
|
||||||
@@ -751,9 +853,14 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
@since version 3.12.0
|
@since version 3.12.0
|
||||||
@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/
|
@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__)) })
|
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, ...) \
|
#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__)) })
|
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__)) })
|
||||||
|
|
||||||
@@ -812,7 +919,3 @@ void templated_json_throw(ExceptionType exception)
|
|||||||
#ifndef JSON_USE_GLOBAL_UDLS
|
#ifndef JSON_USE_GLOBAL_UDLS
|
||||||
#define JSON_USE_GLOBAL_UDLS 1
|
#define JSON_USE_GLOBAL_UDLS 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef JSON_STRICT_NUL_HANDLING
|
|
||||||
#define JSON_STRICT_NUL_HANDLING 0
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@
|
|||||||
#undef JSON_NO_UNIQUE_ADDRESS
|
#undef JSON_NO_UNIQUE_ADDRESS
|
||||||
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
#undef JSON_DISABLE_ENUM_SERIALIZATION
|
||||||
#undef JSON_USE_GLOBAL_UDLS
|
#undef JSON_USE_GLOBAL_UDLS
|
||||||
#undef JSON_STRICT_NUL_HANDLING
|
|
||||||
|
|
||||||
#ifndef JSON_TEST_KEEP_MACROS
|
#ifndef JSON_TEST_KEEP_MACROS
|
||||||
#undef JSON_CATCH
|
#undef JSON_CATCH
|
||||||
@@ -45,6 +44,8 @@
|
|||||||
#undef JSON_HAS_STATIC_RTTI
|
#undef JSON_HAS_STATIC_RTTI
|
||||||
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
#undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
|
||||||
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
#undef JSON_BRACE_INIT_COPY_SEMANTICS
|
||||||
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
|
#undef JSON_STRICT_NUL_HANDLING
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
#include <nlohmann/thirdparty/hedley/hedley_undef.hpp>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
write_bson_object(*j.m_data.m_value.object);
|
write_bson_document(j);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,92 +168,20 @@ class binary_writer
|
|||||||
if (j.m_data.m_value.number_integer >= 0)
|
if (j.m_data.m_value.number_integer >= 0)
|
||||||
{
|
{
|
||||||
// CBOR does not differentiate between positive signed
|
// CBOR does not differentiate between positive signed
|
||||||
// integers and unsigned integers. Therefore, we used the
|
// integers and unsigned integers
|
||||||
// code from the value_t::number_unsigned case here.
|
write_cbor_head(0x00, static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
||||||
if (j.m_data.m_value.number_integer <= 0x17)
|
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x18));
|
|
||||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x19));
|
|
||||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x1A));
|
|
||||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x1B));
|
|
||||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The conversions below encode the sign in the first
|
// a negative integer n is encoded as -1 - n
|
||||||
// byte, and the value is converted to a positive number.
|
write_cbor_head(0x20, static_cast<std::uint64_t>(-1 - j.m_data.m_value.number_integer));
|
||||||
const auto positive_number = -1 - j.m_data.m_value.number_integer;
|
|
||||||
if (j.m_data.m_value.number_integer >= -24)
|
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(0x20 + positive_number));
|
|
||||||
}
|
|
||||||
else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x38));
|
|
||||||
write_number(static_cast<std::uint8_t>(positive_number));
|
|
||||||
}
|
|
||||||
else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x39));
|
|
||||||
write_number(static_cast<std::uint16_t>(positive_number));
|
|
||||||
}
|
|
||||||
else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x3A));
|
|
||||||
write_number(static_cast<std::uint32_t>(positive_number));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x3B));
|
|
||||||
write_number(static_cast<std::uint64_t>(positive_number));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
{
|
{
|
||||||
if (j.m_data.m_value.number_unsigned <= 0x17)
|
write_cbor_head(0x00, j.m_data.m_value.number_unsigned);
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x18));
|
|
||||||
write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x19));
|
|
||||||
write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));
|
|
||||||
}
|
|
||||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x1A));
|
|
||||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x1B));
|
|
||||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,33 +211,7 @@ class binary_writer
|
|||||||
case value_t::string:
|
case value_t::string:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the string length
|
// step 1: write control byte and the string length
|
||||||
const auto N = j.m_data.m_value.string->size();
|
write_cbor_head(0x60, j.m_data.m_value.string->size());
|
||||||
if (N <= 0x17)
|
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(0x60 + N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x78));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x79));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x7A));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x7B));
|
|
||||||
write_number(static_cast<std::uint64_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
// step 2: write the string
|
// step 2: write the string
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
@@ -321,33 +223,7 @@ class binary_writer
|
|||||||
case value_t::array:
|
case value_t::array:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the array size
|
// step 1: write control byte and the array size
|
||||||
const auto N = j.m_data.m_value.array->size();
|
write_cbor_head(0x80, j.m_data.m_value.array->size());
|
||||||
if (N <= 0x17)
|
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(0x80 + N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x98));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x99));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x9A));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x9B));
|
|
||||||
write_number(static_cast<std::uint64_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
// step 2: write each element
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.array)
|
for (const auto& el : *j.m_data.m_value.array)
|
||||||
@@ -376,7 +252,7 @@ class binary_writer
|
|||||||
write_number(static_cast<std::uint8_t>(0xda));
|
write_number(static_cast<std::uint8_t>(0xda));
|
||||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
|
write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())
|
else
|
||||||
{
|
{
|
||||||
write_number(static_cast<std::uint8_t>(0xdb));
|
write_number(static_cast<std::uint8_t>(0xdb));
|
||||||
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
|
write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));
|
||||||
@@ -385,32 +261,7 @@ class binary_writer
|
|||||||
|
|
||||||
// step 1: write control byte and the binary array size
|
// step 1: write control byte and the binary array size
|
||||||
const auto N = j.m_data.m_value.binary->size();
|
const auto N = j.m_data.m_value.binary->size();
|
||||||
if (N <= 0x17)
|
write_cbor_head(0x40, N);
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(0x40 + N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x58));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x59));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x5A));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0x5B));
|
|
||||||
write_number(static_cast<std::uint64_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
// step 2: write each element
|
// step 2: write each element
|
||||||
oa.write_characters(
|
oa.write_characters(
|
||||||
@@ -423,33 +274,7 @@ class binary_writer
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
// step 1: write control byte and the object size
|
// step 1: write control byte and the object size
|
||||||
const auto N = j.m_data.m_value.object->size();
|
write_cbor_head(0xA0, j.m_data.m_value.object->size());
|
||||||
if (N <= 0x17)
|
|
||||||
{
|
|
||||||
write_number(static_cast<std::uint8_t>(0xA0 + N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint8_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0xB8));
|
|
||||||
write_number(static_cast<std::uint8_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint16_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0xB9));
|
|
||||||
write_number(static_cast<std::uint16_t>(N));
|
|
||||||
}
|
|
||||||
else if (N <= (std::numeric_limits<std::uint32_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0xBA));
|
|
||||||
write_number(static_cast<std::uint32_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
else if (N <= (std::numeric_limits<std::uint64_t>::max)())
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(0xBB));
|
|
||||||
write_number(static_cast<std::uint64_t>(N));
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
// step 2: write each element
|
// step 2: write each element
|
||||||
for (const auto& el : *j.m_data.m_value.object)
|
for (const auto& el : *j.m_data.m_value.object)
|
||||||
@@ -517,7 +342,7 @@ class binary_writer
|
|||||||
oa.write_character(to_char_type(0xCE));
|
oa.write_character(to_char_type(0xCE));
|
||||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
else
|
||||||
{
|
{
|
||||||
// uint 64
|
// uint 64
|
||||||
oa.write_character(to_char_type(0xCF));
|
oa.write_character(to_char_type(0xCF));
|
||||||
@@ -552,8 +377,7 @@ class binary_writer
|
|||||||
oa.write_character(to_char_type(0xD2));
|
oa.write_character(to_char_type(0xD2));
|
||||||
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&
|
else
|
||||||
j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
|
||||||
{
|
{
|
||||||
// int 64
|
// int 64
|
||||||
oa.write_character(to_char_type(0xD3));
|
oa.write_character(to_char_type(0xD3));
|
||||||
@@ -588,7 +412,7 @@ class binary_writer
|
|||||||
oa.write_character(to_char_type(0xCE));
|
oa.write_character(to_char_type(0xCE));
|
||||||
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));
|
||||||
}
|
}
|
||||||
else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
else
|
||||||
{
|
{
|
||||||
// uint 64
|
// uint 64
|
||||||
oa.write_character(to_char_type(0xCF));
|
oa.write_character(to_char_type(0xCF));
|
||||||
@@ -1197,35 +1021,6 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Writes a BSON element with key @a name and object @a value
|
|
||||||
*/
|
|
||||||
void write_bson_object_entry(const string_t& name,
|
|
||||||
const typename BasicJsonType::object_t& value)
|
|
||||||
{
|
|
||||||
write_bson_entry_header(name, 0x03); // object
|
|
||||||
write_bson_object(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
|
||||||
@return The size of the BSON-encoded array @a value
|
|
||||||
*/
|
|
||||||
static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value)
|
|
||||||
{
|
|
||||||
std::size_t array_index = 0ul;
|
|
||||||
|
|
||||||
const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast<std::size_t>(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el)
|
|
||||||
{
|
|
||||||
// the index is built as a std::string, while calc_bson_element_size
|
|
||||||
// takes a string_t; convert explicitly, as the two are only
|
|
||||||
// implicitly convertible for some string types
|
|
||||||
const auto key = std::to_string(array_index++);
|
|
||||||
return result + calc_bson_element_size(string_t(key.data(), key.size()), el);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sizeof(std::int32_t) + embedded_document_size + 1ul;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@return The size of the BSON-encoded binary array @a value
|
@return The size of the BSON-encoded binary array @a value
|
||||||
*/
|
*/
|
||||||
@@ -1234,29 +1029,6 @@ class binary_writer
|
|||||||
return sizeof(std::int32_t) + value.size() + 1ul;
|
return sizeof(std::int32_t) + value.size() + 1ul;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
|
||||||
@brief Writes a BSON element with key @a name and array @a value
|
|
||||||
*/
|
|
||||||
void write_bson_array(const string_t& name,
|
|
||||||
const typename BasicJsonType::array_t& value)
|
|
||||||
{
|
|
||||||
write_bson_entry_header(name, 0x04); // array
|
|
||||||
write_number<std::int32_t>(to_bson_length(calc_bson_array_size(value)), true);
|
|
||||||
|
|
||||||
std::size_t array_index = 0ul;
|
|
||||||
|
|
||||||
for (const auto& el : value)
|
|
||||||
{
|
|
||||||
// the index is built as a std::string, while write_bson_element takes
|
|
||||||
// a string_t; convert explicitly, as the two are only implicitly
|
|
||||||
// convertible for some string types
|
|
||||||
const auto key = std::to_string(array_index++);
|
|
||||||
write_bson_element(string_t(key.data(), key.size()), el);
|
|
||||||
}
|
|
||||||
|
|
||||||
oa.write_character(to_char_type(0x00));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Writes a BSON element with key @a name and binary value @a value
|
@brief Writes a BSON element with key @a name and binary value @a value
|
||||||
*/
|
*/
|
||||||
@@ -1278,43 +1050,37 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Calculates the size necessary to serialize the JSON value @a j with its @a name
|
@return The size of the value of the BSON document entry for @a j, which
|
||||||
@return The calculated size for the BSON document entry for @a j with the given @a name.
|
is neither an object nor an array
|
||||||
*/
|
*/
|
||||||
static std::size_t calc_bson_element_size(const string_t& name,
|
static std::size_t calc_bson_value_size(const BasicJsonType& j)
|
||||||
const BasicJsonType& j)
|
|
||||||
{
|
{
|
||||||
const auto header_size = calc_bson_entry_header_size(name, j);
|
|
||||||
switch (j.type())
|
switch (j.type())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
return header_size + calc_bson_object_size(*j.m_data.m_value.object);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return header_size + calc_bson_array_size(*j.m_data.m_value.array);
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
return header_size + calc_bson_binary_size(*j.m_data.m_value.binary);
|
return calc_bson_binary_size(*j.m_data.m_value.binary);
|
||||||
|
|
||||||
case value_t::boolean:
|
case value_t::boolean:
|
||||||
return header_size + 1ul;
|
return 1ul;
|
||||||
|
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
return header_size + 8ul;
|
return 8ul;
|
||||||
|
|
||||||
case value_t::number_integer:
|
case value_t::number_integer:
|
||||||
return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer);
|
return calc_bson_integer_size(j.m_data.m_value.number_integer);
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
return calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);
|
||||||
|
|
||||||
case value_t::string:
|
case value_t::string:
|
||||||
return header_size + calc_bson_string_size(*j.m_data.m_value.string);
|
return calc_bson_string_size(*j.m_data.m_value.string);
|
||||||
|
|
||||||
case value_t::null:
|
case value_t::null:
|
||||||
return header_size + 0ul;
|
return 0ul;
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
@@ -1324,22 +1090,13 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief Serializes the JSON value @a j to BSON and associates it with the
|
@brief Writes the BSON document entry with key @a name for @a j, which is
|
||||||
key @a name.
|
neither an object nor an array
|
||||||
@param name The name to associate with the JSON entity @a j within the
|
|
||||||
current BSON document
|
|
||||||
*/
|
*/
|
||||||
void write_bson_element(const string_t& name,
|
void write_bson_value(const string_t& name, const BasicJsonType& j)
|
||||||
const BasicJsonType& j)
|
|
||||||
{
|
{
|
||||||
switch (j.type())
|
switch (j.type())
|
||||||
{
|
{
|
||||||
case value_t::object:
|
|
||||||
return write_bson_object_entry(name, *j.m_data.m_value.object);
|
|
||||||
|
|
||||||
case value_t::array:
|
|
||||||
return write_bson_array(name, *j.m_data.m_value.array);
|
|
||||||
|
|
||||||
case value_t::binary:
|
case value_t::binary:
|
||||||
return write_bson_binary(name, *j.m_data.m_value.binary);
|
return write_bson_binary(name, *j.m_data.m_value.binary);
|
||||||
|
|
||||||
@@ -1362,6 +1119,8 @@ class binary_writer
|
|||||||
return write_bson_null(name);
|
return write_bson_null(name);
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
|
case value_t::object:
|
||||||
|
case value_t::array:
|
||||||
case value_t::discarded:
|
case value_t::discarded:
|
||||||
default:
|
default:
|
||||||
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)
|
||||||
@@ -1370,43 +1129,267 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/// @brief an object or array of the BSON document being sized or written
|
||||||
@brief Calculates the size of the BSON serialization of the given
|
struct bson_frame
|
||||||
JSON-object @a j.
|
|
||||||
@param[in] value JSON value to serialize
|
|
||||||
@pre value.type() == value_t::object
|
|
||||||
*/
|
|
||||||
static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)
|
|
||||||
{
|
{
|
||||||
const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0),
|
explicit bson_frame(const BasicJsonType* value_, const std::size_t size_slot_ = 0)
|
||||||
[](size_t result, const typename BasicJsonType::object_t::value_type & el)
|
: value(value_)
|
||||||
|
, size_slot(size_slot_)
|
||||||
{
|
{
|
||||||
return result += calc_bson_element_size(el.first, el.second);
|
if (value->is_object())
|
||||||
});
|
{
|
||||||
|
member = value->m_data.m_value.object->cbegin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sizeof(std::int32_t) + document_size + 1ul;
|
/// the object or array
|
||||||
|
const BasicJsonType* value;
|
||||||
|
/// objects: the next member
|
||||||
|
typename BasicJsonType::object_t::const_iterator member{};
|
||||||
|
/// arrays: the index of the next element
|
||||||
|
std::size_t index = 0;
|
||||||
|
/// @ref calc_bson_sizes only: where its size goes in the table
|
||||||
|
std::size_t size_slot;
|
||||||
|
/// @ref calc_bson_sizes only: the size of its entries seen so far
|
||||||
|
std::size_t entries_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief creates the name BSON gives the array element with index @a index
|
||||||
|
@param[out] name receives the decimal index
|
||||||
|
*/
|
||||||
|
static void create_bson_index_name(const std::size_t index, string_t& name)
|
||||||
|
{
|
||||||
|
// the index is built as a std::string; convert explicitly, as the
|
||||||
|
// two are only implicitly convertible for some string types
|
||||||
|
const auto key = std::to_string(index);
|
||||||
|
name = string_t(key.data(), key.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@param[in] value JSON value to serialize
|
@brief Calculates the size of every object and array in the BSON document
|
||||||
@pre value.type() == value_t::object
|
@a document, including the document itself.
|
||||||
|
|
||||||
|
BSON prefixes every document and array with its size, so all of them have
|
||||||
|
to be known before the first byte is written. They are computed in a
|
||||||
|
single pass, each one from the sizes of its entries, which keeps
|
||||||
|
serializing linear in the size of the document; computing each size by
|
||||||
|
walking the entire value below it made it quadratic in the nesting depth.
|
||||||
|
The pass keeps the objects and arrays it has entered on an explicit stack,
|
||||||
|
so a deeply nested value cannot exhaust the call stack.
|
||||||
|
|
||||||
|
@param[in] document the JSON object to serialize
|
||||||
|
@param[out] nested_sizes the sizes of the objects and arrays in
|
||||||
|
@a document, in the order they are written
|
||||||
|
@return the size of @a document
|
||||||
|
@throw out_of_range.409 if a key contains U+0000, before anything is
|
||||||
|
written
|
||||||
*/
|
*/
|
||||||
void write_bson_object(const typename BasicJsonType::object_t& value)
|
static std::size_t calc_bson_sizes(const BasicJsonType& document, std::vector<std::size_t>& nested_sizes)
|
||||||
{
|
{
|
||||||
write_number<std::int32_t>(to_bson_length(calc_bson_object_size(value)), true);
|
// the object or array whose entries are being sized, and the ones it
|
||||||
|
// is in; nothing is allocated unless the document nests
|
||||||
|
bson_frame current(&document);
|
||||||
|
std::vector<bson_frame> parents;
|
||||||
|
// string_t need not be default constructible
|
||||||
|
string_t index_name("", 0);
|
||||||
|
|
||||||
for (const auto& el : value)
|
while (true)
|
||||||
{
|
{
|
||||||
write_bson_element(el.first, el.second);
|
// size entries until the current object or array is done, or an
|
||||||
}
|
// entry is an object or array itself
|
||||||
|
const BasicJsonType* nested = nullptr;
|
||||||
|
if (current.value->is_object())
|
||||||
|
{
|
||||||
|
const auto& object = *current.value->m_data.m_value.object;
|
||||||
|
while (nested == nullptr && current.member != object.cend())
|
||||||
|
{
|
||||||
|
const auto& el = *current.member;
|
||||||
|
++current.member;
|
||||||
|
current.entries_size += calc_bson_entry_header_size(el.first, el.second);
|
||||||
|
if (el.second.is_structured())
|
||||||
|
{
|
||||||
|
nested = &el.second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current.entries_size += calc_bson_value_size(el.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto& array = *current.value->m_data.m_value.array;
|
||||||
|
while (nested == nullptr && current.index < array.size())
|
||||||
|
{
|
||||||
|
const BasicJsonType& el = array[current.index];
|
||||||
|
create_bson_index_name(current.index, index_name);
|
||||||
|
current.entries_size += calc_bson_entry_header_size(index_name, el);
|
||||||
|
++current.index;
|
||||||
|
if (el.is_structured())
|
||||||
|
{
|
||||||
|
nested = ⪙
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current.entries_size += calc_bson_value_size(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
oa.write_character(to_char_type(0x00));
|
if (nested != nullptr)
|
||||||
|
{
|
||||||
|
// its size is added to the current one's once it is done
|
||||||
|
nested_sizes.push_back(0);
|
||||||
|
parents.push_back(std::move(current));
|
||||||
|
current = bson_frame(nested, nested_sizes.size() - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the int32 size, the entries, and the terminating null byte
|
||||||
|
const std::size_t size = sizeof(std::int32_t) + current.entries_size + 1ul;
|
||||||
|
if (parents.empty())
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
nested_sizes[current.size_slot] = size;
|
||||||
|
current = std::move(parents.back());
|
||||||
|
parents.pop_back();
|
||||||
|
current.entries_size += size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Serializes the JSON object @a document as a BSON document
|
||||||
|
|
||||||
|
Writes the objects and arrays in it without the call stack, keeping the
|
||||||
|
ones it has entered on an explicit stack, so a deeply nested value
|
||||||
|
cannot exhaust the call stack.
|
||||||
|
|
||||||
|
@param[in] document the JSON object to serialize
|
||||||
|
@pre document.type() == value_t::object
|
||||||
|
*/
|
||||||
|
void write_bson_document(const BasicJsonType& document)
|
||||||
|
{
|
||||||
|
std::vector<std::size_t> nested_sizes;
|
||||||
|
const std::size_t document_size = calc_bson_sizes(document, nested_sizes);
|
||||||
|
write_number<std::int32_t>(to_bson_length(document_size), true);
|
||||||
|
|
||||||
|
// the object or array whose entries are being written, and the ones
|
||||||
|
// it is in
|
||||||
|
bson_frame current(&document);
|
||||||
|
std::vector<bson_frame> parents;
|
||||||
|
std::size_t next_size = 0;
|
||||||
|
// string_t need not be default constructible
|
||||||
|
string_t index_name("", 0);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// write entries until the current object or array is done, or an
|
||||||
|
// entry is an object or array itself
|
||||||
|
const string_t* nested_name = nullptr;
|
||||||
|
const BasicJsonType* nested = nullptr;
|
||||||
|
if (current.value->is_object())
|
||||||
|
{
|
||||||
|
const auto& object = *current.value->m_data.m_value.object;
|
||||||
|
while (nested == nullptr && current.member != object.cend())
|
||||||
|
{
|
||||||
|
const auto& el = *current.member;
|
||||||
|
++current.member;
|
||||||
|
if (el.second.is_structured())
|
||||||
|
{
|
||||||
|
nested_name = &el.first;
|
||||||
|
nested = &el.second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_bson_value(el.first, el.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto& array = *current.value->m_data.m_value.array;
|
||||||
|
while (nested == nullptr && current.index < array.size())
|
||||||
|
{
|
||||||
|
const BasicJsonType& el = array[current.index];
|
||||||
|
create_bson_index_name(current.index, index_name);
|
||||||
|
++current.index;
|
||||||
|
if (el.is_structured())
|
||||||
|
{
|
||||||
|
nested_name = &index_name;
|
||||||
|
nested = ⪙
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_bson_value(index_name, el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nested != nullptr)
|
||||||
|
{
|
||||||
|
write_bson_entry_header(*nested_name, nested->is_object() ? 0x03 : 0x04);
|
||||||
|
write_number<std::int32_t>(to_bson_length(nested_sizes[next_size++]), true);
|
||||||
|
parents.push_back(std::move(current));
|
||||||
|
current = bson_frame(nested);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
oa.write_character(to_char_type(0x00));
|
||||||
|
if (parents.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
current = std::move(parents.back());
|
||||||
|
parents.pop_back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////
|
//////////
|
||||||
// CBOR //
|
// CBOR //
|
||||||
//////////
|
//////////
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief write the head of a CBOR data item
|
||||||
|
|
||||||
|
The head is the major type in the upper three bits of the first byte and
|
||||||
|
an argument - an unsigned integer, the length of a string, the number of
|
||||||
|
elements of a container - in the shortest of its encodings: in the lower
|
||||||
|
five bits of the first byte itself if it is at most 23, otherwise in the
|
||||||
|
1, 2, 4, or 8 bytes that follow (RFC 8949, section 3).
|
||||||
|
|
||||||
|
@param[in] major_type the major type, shifted into the upper three bits
|
||||||
|
@param[in] argument the argument of the data item
|
||||||
|
*/
|
||||||
|
void write_cbor_head(const std::uint8_t major_type, const std::uint64_t argument)
|
||||||
|
{
|
||||||
|
if (argument <= 0x17)
|
||||||
|
{
|
||||||
|
write_number(static_cast<std::uint8_t>(major_type + argument));
|
||||||
|
}
|
||||||
|
else if (argument <= (std::numeric_limits<std::uint8_t>::max)())
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x18)));
|
||||||
|
write_number(static_cast<std::uint8_t>(argument));
|
||||||
|
}
|
||||||
|
else if (argument <= (std::numeric_limits<std::uint16_t>::max)())
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x19)));
|
||||||
|
write_number(static_cast<std::uint16_t>(argument));
|
||||||
|
}
|
||||||
|
else if (argument <= (std::numeric_limits<std::uint32_t>::max)())
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1A)));
|
||||||
|
write_number(static_cast<std::uint32_t>(argument));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(static_cast<std::uint8_t>(major_type + 0x1B)));
|
||||||
|
write_number(argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
|
static constexpr CharType get_cbor_float_prefix(float /*unused*/)
|
||||||
{
|
{
|
||||||
return to_char_type(0xFA); // Single-Precision Float
|
return to_char_type(0xFA); // Single-Precision Float
|
||||||
@@ -1512,7 +1495,7 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||||
}
|
}
|
||||||
else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())
|
else if (use_bjdata)
|
||||||
{
|
{
|
||||||
if (add_prefix)
|
if (add_prefix)
|
||||||
{
|
{
|
||||||
@@ -1592,30 +1575,59 @@ class binary_writer
|
|||||||
}
|
}
|
||||||
write_number(static_cast<uint32_t>(n), use_bjdata);
|
write_number(static_cast<uint32_t>(n), use_bjdata);
|
||||||
}
|
}
|
||||||
else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
|
||||||
{
|
|
||||||
if (add_prefix)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type('L')); // int64
|
|
||||||
}
|
|
||||||
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (add_prefix)
|
// every value of an integer type of at most 64 bits fits into an
|
||||||
{
|
// int64; only a wider type needs a range check
|
||||||
oa.write_character(to_char_type('H')); // high-precision number
|
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata,
|
||||||
}
|
std::integral_constant < bool, std::numeric_limits<NumberType>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||||
|
|
||||||
const auto number = BasicJsonType(n).dump();
|
|
||||||
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
|
||||||
for (std::size_t i = 0; i < number.size(); ++i)
|
|
||||||
{
|
|
||||||
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// LCOV_EXCL_STOP
|
}
|
||||||
|
|
||||||
|
template<typename NumberType>
|
||||||
|
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::true_type /*fits_int64*/)
|
||||||
|
{
|
||||||
|
if (add_prefix)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type('L')); // int64
|
||||||
|
}
|
||||||
|
write_number(static_cast<std::int64_t>(n), use_bjdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NumberType>
|
||||||
|
void write_ubjson_int64_or_high_precision(const NumberType n, const bool add_prefix, const bool use_bjdata, std::false_type /*fits_int64*/)
|
||||||
|
{
|
||||||
|
if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())
|
||||||
|
{
|
||||||
|
write_ubjson_int64_or_high_precision(n, add_prefix, use_bjdata, std::true_type {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add_prefix)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type('H')); // high-precision number
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto number = BasicJsonType(n).dump();
|
||||||
|
write_number_with_ubjson_prefix(number.size(), true, use_bjdata);
|
||||||
|
for (std::size_t i = 0; i < number.size(); ++i)
|
||||||
|
{
|
||||||
|
oa.write_character(to_char_type(static_cast<std::uint8_t>(number[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NumberType>
|
||||||
|
static constexpr CharType ubjson_int64_or_high_precision_prefix(const NumberType /*n*/, std::true_type /*fits_int64*/) noexcept
|
||||||
|
{
|
||||||
|
return 'L';
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename NumberType>
|
||||||
|
static CharType ubjson_int64_or_high_precision_prefix(const NumberType n, std::false_type /*fits_int64*/) noexcept
|
||||||
|
{
|
||||||
|
// anything outside of the range of an int64 is treated as a
|
||||||
|
// high-precision number
|
||||||
|
return ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)()) ? 'L' : 'H';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@@ -1657,12 +1669,10 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
return 'm';
|
return 'm';
|
||||||
}
|
}
|
||||||
if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())
|
// every value of an integer type of at most 64 bits fits into
|
||||||
{
|
// an int64; only a wider type needs a range check
|
||||||
return 'L';
|
return ubjson_int64_or_high_precision_prefix(j.m_data.m_value.number_integer,
|
||||||
}
|
std::integral_constant < bool, std::numeric_limits<typename BasicJsonType::number_integer_t>::digits <= std::numeric_limits<std::int64_t>::digits > {});
|
||||||
// anything else is treated as a high-precision number
|
|
||||||
return 'H'; // LCOV_EXCL_LINE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_unsigned:
|
case value_t::number_unsigned:
|
||||||
@@ -1695,12 +1705,12 @@ class binary_writer
|
|||||||
{
|
{
|
||||||
return 'L';
|
return 'L';
|
||||||
}
|
}
|
||||||
if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())
|
if (use_bjdata)
|
||||||
{
|
{
|
||||||
return 'M';
|
return 'M';
|
||||||
}
|
}
|
||||||
// anything else is treated as a high-precision number
|
// anything else is treated as a high-precision number
|
||||||
return 'H'; // LCOV_EXCL_LINE
|
return 'H';
|
||||||
}
|
}
|
||||||
|
|
||||||
case value_t::number_float:
|
case value_t::number_float:
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <nlohmann/detail/output/output_adapters.hpp>
|
#include <nlohmann/detail/output/output_adapters.hpp>
|
||||||
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
#include <nlohmann/detail/recursion_depth_limit.hpp>
|
||||||
#include <nlohmann/detail/string_concat.hpp>
|
#include <nlohmann/detail/string_concat.hpp>
|
||||||
|
#include <nlohmann/detail/string_utils.hpp>
|
||||||
#include <nlohmann/detail/value_t.hpp>
|
#include <nlohmann/detail/value_t.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
@@ -58,8 +59,6 @@ class serializer
|
|||||||
using number_integer_t = typename BasicJsonType::number_integer_t;
|
using number_integer_t = typename BasicJsonType::number_integer_t;
|
||||||
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
|
||||||
using binary_char_t = typename BasicJsonType::binary_t::value_type;
|
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:
|
public:
|
||||||
/*!
|
/*!
|
||||||
@@ -1592,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
|
* Overload to make the compiler happy while it is instantiating
|
||||||
* dump_integer for number_unsigned_t.
|
* dump_integer for number_unsigned_t.
|
||||||
|
|||||||
@@ -8,10 +8,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array> // array
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t, uint32_t
|
||||||
#include <string> // string, to_string
|
#include <string> // string, to_string
|
||||||
|
|
||||||
#include <nlohmann/detail/abi_macros.hpp>
|
#include <nlohmann/detail/abi_macros.hpp>
|
||||||
|
#include <nlohmann/detail/macro_scope.hpp>
|
||||||
|
|
||||||
NLOHMANN_JSON_NAMESPACE_BEGIN
|
NLOHMANN_JSON_NAMESPACE_BEGIN
|
||||||
namespace detail
|
namespace detail
|
||||||
@@ -33,5 +36,102 @@ StringType to_string(std::size_t value)
|
|||||||
return result;
|
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
|
} // namespace detail
|
||||||
NLOHMANN_JSON_NAMESPACE_END
|
NLOHMANN_JSON_NAMESPACE_END
|
||||||
|
|||||||
+334
-34
@@ -924,6 +924,31 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief whether a descent must stop here and finish without the call stack
|
||||||
|
|
||||||
|
@a may_descend says whether the operator descends at all; it is a constant
|
||||||
|
at every call site, and is passed rather than tested by the caller so that
|
||||||
|
the test does not become a constant condition there, which MSVC reports as
|
||||||
|
C4127.
|
||||||
|
|
||||||
|
The comparison operators use this rather than @ref nesting_depth_guard::okay,
|
||||||
|
because they are written as a macro and a macro cannot use the preprocessor
|
||||||
|
the way the guard's constructor does; @ref copy_structured, which can, asks
|
||||||
|
the guard instead and never calls this.
|
||||||
|
*/
|
||||||
|
static bool nesting_depth_exhausted(bool may_descend = true) noexcept
|
||||||
|
{
|
||||||
|
#ifdef JSON_NO_THREAD_LOCAL
|
||||||
|
// without a count of its own per thread, a descent cannot be bounded
|
||||||
|
// without racing another one, so none is made
|
||||||
|
static_cast<void>(may_descend);
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return !may_descend || nesting_depth() >= nesting_depth_limit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@brief counts one level of a bounded descent for as long as it runs, and
|
@brief counts one level of a bounded descent for as long as it runs, and
|
||||||
reports whether the descent was still within the limit when it began
|
reports whether the descent was still within the limit when it began
|
||||||
@@ -978,7 +1003,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
using copy_worklist_t = std::vector<std::pair<const basic_json*, basic_json*>>;
|
using copy_worklist_t = std::vector<std::pair<const basic_json*, basic_json*>>;
|
||||||
|
|
||||||
/// scratch space to build the key skeleton of an object copy in one go
|
/// scratch space to build the key skeleton of an object copy in one go
|
||||||
using copy_scratch_t = std::vector<std::pair<typename object_t::key_type, basic_json>>;
|
using copy_scratch_value_t = std::pair<typename object_t::key_type, basic_json>;
|
||||||
|
using copy_scratch_t = std::vector<copy_scratch_value_t, AllocatorType<copy_scratch_value_t>>;
|
||||||
|
|
||||||
/// @brief copy everything of @a src into @a dst but its type and value
|
/// @brief copy everything of @a src into @a dst but its type and value
|
||||||
static void copy_metadata(const basic_json& src, basic_json& dst)
|
static void copy_metadata(const basic_json& src, basic_json& dst)
|
||||||
@@ -1243,6 +1269,274 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// the result of comparing two values, including values that cannot be
|
||||||
|
/// ordered at all, such as a discarded value or a NaN
|
||||||
|
enum class compare_result { less, equal, greater, unordered };
|
||||||
|
|
||||||
|
#if JSON_HAS_THREE_WAY_COMPARISON
|
||||||
|
/// @brief the ordering that @a result stands for
|
||||||
|
static std::partial_ordering to_partial_ordering(compare_result result) noexcept // *NOPAD*
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case compare_result::less:
|
||||||
|
return std::partial_ordering::less;
|
||||||
|
case compare_result::greater:
|
||||||
|
return std::partial_ordering::greater;
|
||||||
|
case compare_result::equal:
|
||||||
|
return std::partial_ordering::equivalent;
|
||||||
|
case compare_result::unordered:
|
||||||
|
default:
|
||||||
|
return std::partial_ordering::unordered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare two values that are not both an array or both an object
|
||||||
|
|
||||||
|
Such a pair is compared by the operators themselves, which cannot descend
|
||||||
|
into it and therefore cannot recurse.
|
||||||
|
|
||||||
|
That holds for a pair whose types differ as much as for a pair of leaves: an
|
||||||
|
array and an object are told apart by their types alone, because an operator
|
||||||
|
only ever descends into two values of the same type. So `==` reports them as
|
||||||
|
unequal without looking inside either, and an ordering falls back to the
|
||||||
|
order of the types - an object sorts before an array - exactly as it does
|
||||||
|
for a value that is not nested deeply enough to get here.
|
||||||
|
*/
|
||||||
|
template<bool Ordered>
|
||||||
|
static compare_result compare_leaves(const_reference lhs, const_reference rhs) noexcept
|
||||||
|
{
|
||||||
|
if (lhs == rhs)
|
||||||
|
{
|
||||||
|
return compare_result::equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return order_leaves(lhs, rhs, std::integral_constant<bool, Ordered> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare two object keys
|
||||||
|
|
||||||
|
An object compares its entries as pairs of a key and a value, so its keys
|
||||||
|
are compared exactly as std::pair compares them: with < where the objects
|
||||||
|
are being ordered, and with == where they are only checked for equality.
|
||||||
|
Note that this is not the object's own comparator, which for a vector-backed
|
||||||
|
object type such as nlohmann::ordered_map tells equality rather than order.
|
||||||
|
*/
|
||||||
|
static compare_result compare_keys(const typename object_t::key_type& lhs,
|
||||||
|
const typename object_t::key_type& rhs,
|
||||||
|
std::true_type /*ordered*/)
|
||||||
|
{
|
||||||
|
if (lhs < rhs)
|
||||||
|
{
|
||||||
|
return compare_result::less;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rhs < lhs)
|
||||||
|
{
|
||||||
|
return compare_result::greater;
|
||||||
|
}
|
||||||
|
|
||||||
|
return compare_result::equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief check two object keys for equality
|
||||||
|
static compare_result compare_keys(const typename object_t::key_type& lhs,
|
||||||
|
const typename object_t::key_type& rhs,
|
||||||
|
std::false_type /*ordered*/)
|
||||||
|
{
|
||||||
|
return lhs == rhs ? compare_result::equal : compare_result::unordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief tell apart two values that are not equal
|
||||||
|
/// @note only instantiated where the values are being ordered, as a key or
|
||||||
|
/// string type is not required to be ordered to be compared for equality
|
||||||
|
static compare_result order_leaves(const_reference lhs, const_reference rhs, std::true_type /*ordered*/) noexcept
|
||||||
|
{
|
||||||
|
if (lhs < rhs)
|
||||||
|
{
|
||||||
|
return compare_result::less;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rhs < lhs)
|
||||||
|
{
|
||||||
|
return compare_result::greater;
|
||||||
|
}
|
||||||
|
|
||||||
|
return compare_result::unordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief report two values as not equal without ordering them
|
||||||
|
static compare_result order_leaves(const_reference /*lhs*/, const_reference /*rhs*/, std::false_type /*ordered*/) noexcept
|
||||||
|
{
|
||||||
|
return compare_result::unordered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief compare @a lhs and @a rhs without descending into them
|
||||||
|
|
||||||
|
Reached once a comparison has descended @ref nesting_depth_limit levels, so
|
||||||
|
that comparing values cannot exhaust the call stack however deeply they are
|
||||||
|
nested. The two values are walked in lockstep on an explicit stack and
|
||||||
|
compared lexicographically, element by element in the order the containers
|
||||||
|
enumerate them - which is how the container types this library ships compare
|
||||||
|
themselves: a std::map enumerates its entries in key order, and
|
||||||
|
nlohmann::ordered_map in insertion order. An object type that enumerates its
|
||||||
|
entries in an unspecified order, such as std::unordered_map, compares them
|
||||||
|
pairwise instead; the difference could only ever show below the bound.
|
||||||
|
|
||||||
|
Note that the stack this walks with is allocated, while the comparison
|
||||||
|
operators are noexcept and the container comparison this replaces allocated
|
||||||
|
nothing. Failing that allocation therefore ends the process rather than
|
||||||
|
throwing. It only arises for values nested past the bound, and only when
|
||||||
|
memory has run out - where the same comparison used to exhaust the call
|
||||||
|
stack instead - but it is a way to fail that the operators did not have.
|
||||||
|
*/
|
||||||
|
template<bool Ordered>
|
||||||
|
static compare_result compare_iteratively(const_reference lhs, const_reference rhs,
|
||||||
|
const bool unordered_compares_equal) noexcept
|
||||||
|
{
|
||||||
|
/// a pair of containers being compared in lockstep
|
||||||
|
struct frame
|
||||||
|
{
|
||||||
|
const basic_json* lhs_value{nullptr};
|
||||||
|
const basic_json* rhs_value{nullptr};
|
||||||
|
typename array_t::const_iterator lhs_array_it{};
|
||||||
|
typename array_t::const_iterator rhs_array_it{};
|
||||||
|
typename object_t::const_iterator lhs_object_it{};
|
||||||
|
typename object_t::const_iterator rhs_object_it{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<frame> stack;
|
||||||
|
const basic_json* left = &lhs;
|
||||||
|
const basic_json* right = &rhs;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
const auto type = left->m_data.m_type;
|
||||||
|
|
||||||
|
if (type == right->m_data.m_type && (type == value_t::array || type == value_t::object))
|
||||||
|
{
|
||||||
|
// descend: the elements decide, and are compared further down
|
||||||
|
stack.emplace_back();
|
||||||
|
frame& pushed = stack.back();
|
||||||
|
pushed.lhs_value = left;
|
||||||
|
pushed.rhs_value = right;
|
||||||
|
|
||||||
|
if (type == value_t::array)
|
||||||
|
{
|
||||||
|
pushed.lhs_array_it = left->m_data.m_value.array->cbegin();
|
||||||
|
pushed.rhs_array_it = right->m_data.m_value.array->cbegin();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pushed.lhs_object_it = left->m_data.m_value.object->cbegin();
|
||||||
|
pushed.rhs_object_it = right->m_data.m_value.object->cbegin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const compare_result result = compare_leaves<Ordered>(*left, *right);
|
||||||
|
|
||||||
|
// Values that cannot be ordered - a NaN, say - end an ordered
|
||||||
|
// comparison for std::lexicographical_compare_three_way, but
|
||||||
|
// std::lexicographical_compare treats them as equivalent and
|
||||||
|
// carries on with the next element. Both are reproduced here,
|
||||||
|
// so that a value nested too deeply to descend into compares
|
||||||
|
// exactly as one that is not.
|
||||||
|
if (result != compare_result::equal &&
|
||||||
|
!(unordered_compares_equal && result == compare_result::unordered))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk back up past the containers that are exhausted, then take the
|
||||||
|
// next pair of elements from the innermost one that is not
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
if (stack.empty())
|
||||||
|
{
|
||||||
|
return compare_result::equal;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame& current = stack.back();
|
||||||
|
const bool is_object = current.lhs_value->m_data.m_type == value_t::object;
|
||||||
|
|
||||||
|
const bool lhs_done = is_object
|
||||||
|
? current.lhs_object_it == current.lhs_value->m_data.m_value.object->cend()
|
||||||
|
: current.lhs_array_it == current.lhs_value->m_data.m_value.array->cend();
|
||||||
|
const bool rhs_done = is_object
|
||||||
|
? current.rhs_object_it == current.rhs_value->m_data.m_value.object->cend()
|
||||||
|
: current.rhs_array_it == current.rhs_value->m_data.m_value.array->cend();
|
||||||
|
|
||||||
|
if (lhs_done || rhs_done)
|
||||||
|
{
|
||||||
|
// whichever ran out first holds the smaller container; if
|
||||||
|
// both did, they are equal and the container above decides
|
||||||
|
if (lhs_done != rhs_done)
|
||||||
|
{
|
||||||
|
return lhs_done ? compare_result::less : compare_result::greater;
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.pop_back();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object)
|
||||||
|
{
|
||||||
|
// an entry is a key and a value, and the key decides first
|
||||||
|
const compare_result key_result =
|
||||||
|
compare_keys(current.lhs_object_it->first, current.rhs_object_it->first,
|
||||||
|
std::integral_constant<bool, Ordered> {});
|
||||||
|
|
||||||
|
if (key_result != compare_result::equal)
|
||||||
|
{
|
||||||
|
return key_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
left = &(current.lhs_object_it->second);
|
||||||
|
right = &(current.rhs_object_it->second);
|
||||||
|
++current.lhs_object_it;
|
||||||
|
++current.rhs_object_it;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
left = &(*current.lhs_array_it);
|
||||||
|
right = &(*current.rhs_array_it);
|
||||||
|
++current.lhs_array_it;
|
||||||
|
++current.rhs_array_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief restore the parent pointers after erasing from an object
|
||||||
|
/// ordered_json keeps its members in a vector, and erasing a member
|
||||||
|
/// re-constructs every member after it in place, which resets their
|
||||||
|
/// parent pointers
|
||||||
|
void set_parents_after_object_erase()
|
||||||
|
{
|
||||||
|
#if JSON_DIAGNOSTICS
|
||||||
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
|
#pragma warning(push )
|
||||||
|
#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr
|
||||||
|
#endif
|
||||||
|
if (detail::is_ordered_map<object_t>::value)
|
||||||
|
{
|
||||||
|
set_parents();
|
||||||
|
}
|
||||||
|
#ifdef JSON_HEDLEY_MSVC_VERSION
|
||||||
|
#pragma warning( pop )
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
// JSON parser callback //
|
// JSON parser callback //
|
||||||
@@ -1863,17 +2157,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// value access //
|
// value access //
|
||||||
//////////////////
|
//////////////////
|
||||||
|
|
||||||
/// get a boolean (explicit)
|
|
||||||
boolean_t get_impl(boolean_t* /*unused*/) const
|
|
||||||
{
|
|
||||||
if (JSON_HEDLEY_LIKELY(is_boolean()))
|
|
||||||
{
|
|
||||||
return m_data.m_value.boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSON_THROW(type_error::create(302, detail::concat("type must be boolean, but is ", type_name()), this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get a pointer to the value (object)
|
/// get a pointer to the value (object)
|
||||||
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
object_t* get_impl_ptr(object_t* /*unused*/) noexcept
|
||||||
{
|
{
|
||||||
@@ -2260,6 +2543,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
ValueType & get_to(ValueType& v) const noexcept(noexcept(
|
ValueType & get_to(ValueType& v) const noexcept(noexcept(
|
||||||
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
|
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
|
||||||
{
|
{
|
||||||
|
static_assert(!std::is_const<ValueType>::value, "get_to() cannot deserialize into a const value");
|
||||||
JSONSerializer<ValueType>::from_json(*this, v);
|
JSONSerializer<ValueType>::from_json(*this, v);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@@ -2285,6 +2569,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
noexcept(noexcept(JSONSerializer<Array>::from_json(
|
noexcept(noexcept(JSONSerializer<Array>::from_json(
|
||||||
std::declval<const basic_json_t&>(), v)))
|
std::declval<const basic_json_t&>(), v)))
|
||||||
{
|
{
|
||||||
|
static_assert(!std::is_const<T>::value, "get_to() cannot deserialize into a const value");
|
||||||
JSONSerializer<Array>::from_json(*this, v);
|
JSONSerializer<Array>::from_json(*this, v);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
@@ -2931,6 +3216,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
case value_t::object:
|
case value_t::object:
|
||||||
{
|
{
|
||||||
result.m_it.object_iterator = erase_from_object(pos.m_it.object_iterator);
|
result.m_it.object_iterator = erase_from_object(pos.m_it.object_iterator);
|
||||||
|
set_parents_after_object_erase();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3003,6 +3289,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
{
|
{
|
||||||
result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator,
|
result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator,
|
||||||
last.m_it.object_iterator);
|
last.m_it.object_iterator);
|
||||||
|
set_parents_after_object_erase();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3033,7 +3320,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
|
JSON_THROW(type_error::create(307, detail::concat("cannot use erase() with ", type_name()), this));
|
||||||
}
|
}
|
||||||
|
|
||||||
return m_data.m_value.object->erase(std::forward<KeyType>(key));
|
const auto erased = m_data.m_value.object->erase(std::forward<KeyType>(key));
|
||||||
|
set_parents_after_object_erase();
|
||||||
|
return erased;
|
||||||
}
|
}
|
||||||
|
|
||||||
template < typename KeyType, detail::enable_if_t <
|
template < typename KeyType, detail::enable_if_t <
|
||||||
@@ -3050,6 +3339,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
if (it != m_data.m_value.object->end())
|
if (it != m_data.m_value.object->end())
|
||||||
{
|
{
|
||||||
m_data.m_value.object->erase(it);
|
m_data.m_value.object->erase(it);
|
||||||
|
set_parents_after_object_erase();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -3961,16 +4251,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
if (it2 != m_data.m_value.object->end() && it2->second.is_object())
|
||||||
{
|
{
|
||||||
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
it2->second.update_members(it.value().cbegin(), it.value().cend(), true, depth + 1);
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
it2->second.set_parents();
|
|
||||||
#endif
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_data.m_value.object->operator[](it.key()) = it.value();
|
// set_parent() also repairs the other members, which ordered_json
|
||||||
#if JSON_DIAGNOSTICS
|
// relocates when adding a key makes its vector grow
|
||||||
m_data.m_value.object->operator[](it.key()).m_parent = this;
|
set_parent(m_data.m_value.object->operator[](it.key()) = it.value());
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3999,9 +4285,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a nested object is merged: continue with its parent
|
// a nested object is merged: continue with its parent
|
||||||
#if JSON_DIAGNOSTICS
|
|
||||||
target->set_parents();
|
|
||||||
#endif
|
|
||||||
target = stack.back().target;
|
target = stack.back().target;
|
||||||
first = stack.back().position;
|
first = stack.back().position;
|
||||||
last = stack.back().last;
|
last = stack.back().last;
|
||||||
@@ -4023,10 +4306,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
target->m_data.m_value.object->operator[](first.key()) = first.value();
|
// set_parent() also repairs the other members, which ordered_json
|
||||||
#if JSON_DIAGNOSTICS
|
// relocates when adding a key makes its vector grow
|
||||||
target->m_data.m_value.object->operator[](first.key()).m_parent = target;
|
target->set_parent(target->m_data.m_value.object->operator[](first.key()) = first.value());
|
||||||
#endif
|
|
||||||
++first;
|
++first;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4165,7 +4447,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// because any negative signed value is smaller than any unsigned value.
|
// because any negative signed value is smaller than any unsigned value.
|
||||||
// Otherwise, the non-negative signed value is cast to unsigned before the
|
// Otherwise, the non-negative signed value is cast to unsigned before the
|
||||||
// comparison to avoid wraparound.
|
// comparison to avoid wraparound.
|
||||||
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \
|
#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result, deep_result, may_descend) \
|
||||||
const auto lhs_type = lhs.type(); \
|
const auto lhs_type = lhs.type(); \
|
||||||
const auto rhs_type = rhs.type(); \
|
const auto rhs_type = rhs.type(); \
|
||||||
\
|
\
|
||||||
@@ -4174,11 +4456,25 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
switch (lhs_type) \
|
switch (lhs_type) \
|
||||||
{ \
|
{ \
|
||||||
case value_t::array: \
|
case value_t::array: \
|
||||||
|
{ \
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(nesting_depth_exhausted(may_descend))) \
|
||||||
|
{ \
|
||||||
|
return (deep_result); \
|
||||||
|
} \
|
||||||
|
const nesting_depth_guard guard; \
|
||||||
return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array); \
|
return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array); \
|
||||||
\
|
} \
|
||||||
|
\
|
||||||
case value_t::object: \
|
case value_t::object: \
|
||||||
|
{ \
|
||||||
|
if (JSON_HEDLEY_UNLIKELY(nesting_depth_exhausted(may_descend))) \
|
||||||
|
{ \
|
||||||
|
return (deep_result); \
|
||||||
|
} \
|
||||||
|
const nesting_depth_guard guard; \
|
||||||
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object); \
|
||||||
\
|
} \
|
||||||
|
\
|
||||||
case value_t::null: \
|
case value_t::null: \
|
||||||
return (null_result); \
|
return (null_result); \
|
||||||
\
|
\
|
||||||
@@ -4278,7 +4574,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
const_reference lhs = *this;
|
const_reference lhs = *this;
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
||||||
|
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
@@ -4303,7 +4600,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
|
JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*
|
||||||
std::partial_ordering::equivalent,
|
std::partial_ordering::equivalent,
|
||||||
std::partial_ordering::unordered,
|
std::partial_ordering::unordered,
|
||||||
lhs_type <=> rhs_type) // *NOPAD*
|
lhs_type <=> rhs_type, // *NOPAD*
|
||||||
|
to_partial_ordering(compare_iteratively<true>(lhs, rhs, false)), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: 3-way
|
/// @brief comparison: 3-way
|
||||||
@@ -4370,7 +4668,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
JSON_HEDLEY_DIAGNOSTIC_PUSH
|
||||||
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
JSON_HEDLEY_PRAGMA(GCC diagnostic ignored "-Wfloat-equal")
|
||||||
#endif
|
#endif
|
||||||
JSON_IMPLEMENT_OPERATOR( ==, true, false, false)
|
JSON_IMPLEMENT_OPERATOR( ==, true, false, false,
|
||||||
|
compare_iteratively<false>(lhs, rhs, false) == compare_result::equal, true)
|
||||||
#ifdef __GNUC__
|
#ifdef __GNUC__
|
||||||
JSON_HEDLEY_DIAGNOSTIC_POP
|
JSON_HEDLEY_DIAGNOSTIC_POP
|
||||||
#endif
|
#endif
|
||||||
@@ -4426,7 +4725,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
|
|||||||
// default_result is used if we cannot compare values. In that case,
|
// default_result is used if we cannot compare values. In that case,
|
||||||
// we compare types. Note we have to call the operator explicitly,
|
// we compare types. Note we have to call the operator explicitly,
|
||||||
// because MSVC has problems otherwise.
|
// because MSVC has problems otherwise.
|
||||||
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type))
|
JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type),
|
||||||
|
compare_iteratively<true>(lhs, rhs, true) == compare_result::less, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief comparison: less than
|
/// @brief comparison: less than
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+1147
-455
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,14 @@
|
|||||||
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
|
||||||
#endif
|
#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
|
#if JSON_DIAGNOSTICS
|
||||||
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
|
||||||
#else
|
#else
|
||||||
@@ -80,21 +88,35 @@
|
|||||||
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
|
||||||
#endif
|
#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
|
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
|
||||||
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Construct the namespace ABI tags component
|
// Construct the namespace ABI tags component
|
||||||
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
|
#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) \
|
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d, e, f) \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
|
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d, e, f)
|
||||||
|
|
||||||
#define NLOHMANN_JSON_ABI_TAGS \
|
#define NLOHMANN_JSON_ABI_TAGS \
|
||||||
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
|
||||||
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
|
||||||
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
|
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_BRACE_INIT_COPY_SEMANTICS, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_PRECISE_STREAM_POSITION, \
|
||||||
|
NLOHMANN_JSON_ABI_TAG_STRICT_NUL_HANDLING)
|
||||||
|
|
||||||
// Construct the namespace version component
|
// Construct the namespace version component
|
||||||
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ target_compile_options(test_main PUBLIC
|
|||||||
# std::allocator_traits<...>::construct for the custom-base-class
|
# std::allocator_traits<...>::construct for the custom-base-class
|
||||||
# test's map type past VS2015's limit. The name is only used for
|
# test's map type past VS2015's limit. The name is only used for
|
||||||
# debug info, so truncation does not affect the build.
|
# debug info, so truncation does not affect the build.
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/W4;/wd4566;/wd4996;/wd4702;/wd4503>
|
# 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
|
# https://github.com/nlohmann/json/issues/1114
|
||||||
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
$<$<CXX_COMPILER_ID:MSVC>:/bigobj> $<$<BOOL:${MINGW}>:-Wa,-mbig-obj>
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ TEST_CASE("default namespace")
|
|||||||
expected += "_bics";
|
expected += "_bics";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
|
expected += "_psp";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_STRICT_NUL_HANDLING
|
||||||
|
expected += "_snul";
|
||||||
|
#endif
|
||||||
|
|
||||||
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
expected += "_v" STRINGIZE(NLOHMANN_JSON_VERSION_MAJOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_MINOR);
|
||||||
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
expected += "_" STRINGIZE(NLOHMANN_JSON_VERSION_PATCH) "::basic_json";
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ TEST_CASE("default namespace without version component")
|
|||||||
expected += "_bics";
|
expected += "_bics";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if JSON_PRECISE_STREAM_POSITION
|
||||||
|
expected += "_psp";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if JSON_STRICT_NUL_HANDLING
|
||||||
|
expected += "_snul";
|
||||||
|
#endif
|
||||||
|
|
||||||
expected += "::basic_json";
|
expected += "::basic_json";
|
||||||
|
|
||||||
// fallback for Clang
|
// fallback for Clang
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.11...3.14)
|
cmake_minimum_required(VERSION 3.14)
|
||||||
project(JSON_Benchmarks LANGUAGES CXX)
|
project(JSON_Benchmarks LANGUAGES CXX)
|
||||||
|
|
||||||
# set compiler flags
|
# 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")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -flto -DNDEBUG -O3")
|
||||||
endif()
|
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)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
|
||||||
benchmark
|
|
||||||
GIT_REPOSITORY https://github.com/google/benchmark.git
|
|
||||||
GIT_TAG origin/main
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_GetProperties(benchmark)
|
# only the library is needed; -Werror would break the pinned release as soon as
|
||||||
if(NOT benchmark_POPULATED)
|
# a newer compiler adds a warning
|
||||||
FetchContent_Populate(benchmark)
|
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
||||||
set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "" FORCE)
|
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(${benchmark_SOURCE_DIR} ${benchmark_BINARY_DIR})
|
set(BENCHMARK_ENABLE_WERROR OFF CACHE BOOL "" FORCE)
|
||||||
endif()
|
|
||||||
|
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
|
# download test data
|
||||||
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake ${CMAKE_MODULE_PATH})
|
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../cmake ${CMAKE_MODULE_PATH})
|
||||||
include(download_test_data)
|
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
|
# benchmark binary
|
||||||
add_executable(json_benchmarks src/benchmarks.cpp)
|
add_executable(json_benchmarks src/benchmarks.cpp)
|
||||||
target_compile_features(json_benchmarks PRIVATE cxx_std_11)
|
target_compile_features(json_benchmarks PRIVATE cxx_std_11)
|
||||||
target_link_libraries(json_benchmarks benchmark ${CMAKE_THREAD_LIBS_INIT})
|
target_link_libraries(json_benchmarks benchmark ${CMAKE_THREAD_LIBS_INIT})
|
||||||
add_dependencies(json_benchmarks download_test_data)
|
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())
|
while (state.KeepRunning())
|
||||||
{
|
{
|
||||||
j.dump(indent);
|
std::string output = j.dump(indent);
|
||||||
|
benchmark::DoNotOptimize(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.SetBytesProcessed(state.iterations() * j.dump(indent).size());
|
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.
|
[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.
|
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.
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ dump() serializes any non-finite double the same deterministic way (as JSON
|
|||||||
`null`, since JSON itself cannot represent NaN/Infinity), so comparing
|
`null`, since JSON itself cannot represent NaN/Infinity), so comparing
|
||||||
dumps is stable under exactly the same values that break operator==.
|
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
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ array data, it performs the following steps:
|
|||||||
- j4 = from_ubjson(vec3)
|
- j4 = from_ubjson(vec3)
|
||||||
- assert(j1 == j4)
|
- 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
|
The provided function `LLVMFuzzerTestOneInput` can be used in different fuzzer
|
||||||
drivers.
|
drivers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -270,6 +270,82 @@ TEST_CASE("controlled bad_alloc")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
namespace
|
||||||
{
|
{
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ using nlohmann::json;
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -2867,6 +2868,21 @@ TEST_CASE("BJData")
|
|||||||
const auto out_num = json::to_bjdata(j_num);
|
const auto out_num = json::to_bjdata(j_num);
|
||||||
CHECK(out_num.at(0) == '{');
|
CHECK(out_num.at(0) == '{');
|
||||||
CHECK(json::from_bjdata(out_num) == j_num);
|
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")
|
SECTION("ndarray with out-of-range _ArrayData_ elements stays as object")
|
||||||
@@ -4273,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())
|
TEST_CASE("BJData roundtrips" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("input from self-generated BJData files")
|
SECTION("input from self-generated BJData files")
|
||||||
|
|||||||
+138
-1
@@ -49,7 +49,7 @@ using huge_binary_json = nlohmann::basic_json <
|
|||||||
// for *object keys* (e.g. "s" or "nested" below). Only the designated test
|
// for *object keys* (e.g. "s" or "nested" below). Only the designated test
|
||||||
// value is meant to lie about its size - if every huge_string_t (including
|
// value is meant to lie about its size - if every huge_string_t (including
|
||||||
// keys) reported a huge size, the running totals computed while walking the
|
// keys) reported a huge size, the running totals computed while walking the
|
||||||
// BSON document (see calc_bson_object_size & friends in binary_writer.hpp)
|
// BSON document (see calc_bson_sizes in binary_writer.hpp)
|
||||||
// would need more than 32 bits, and on platforms where std::size_t is only
|
// would need more than 32 bits, and on platforms where std::size_t is only
|
||||||
// 32 bits wide that arithmetic would silently wrap around, producing wrong
|
// 32 bits wide that arithmetic would silently wrap around, producing wrong
|
||||||
// (or even unguarded) lengths. The fake size is therefore opt-in via
|
// (or even unguarded) lengths. The fake size is therefore opt-in via
|
||||||
@@ -1697,3 +1697,140 @@ TEST_CASE("BSON roundtrips" * doctest::skip())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("BSON: deeply nested values")
|
||||||
|
{
|
||||||
|
SECTION("documents and arrays round-trip at every depth")
|
||||||
|
{
|
||||||
|
// nested documents and arrays, with siblings on every level, so
|
||||||
|
// every length prefix covers entries of both kinds
|
||||||
|
json value = "leaf";
|
||||||
|
for (std::size_t depth = 0; depth <= 300; ++depth)
|
||||||
|
{
|
||||||
|
CAPTURE(depth);
|
||||||
|
const json document = {{"value", value}, {"n", depth}};
|
||||||
|
CHECK(json::from_bson(json::to_bson(document)) == document);
|
||||||
|
|
||||||
|
value = depth % 2 == 0 ? json{{"a", std::move(value)}, {"b", {1, "x"}}} :
|
||||||
|
json::array({std::move(value), depth, json::object()});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a key containing U+0000 is rejected before anything is written")
|
||||||
|
{
|
||||||
|
json value = json::object({{std::string("bad\0key", 7), 1}});
|
||||||
|
for (std::size_t depth = 0; depth < 200; ++depth)
|
||||||
|
{
|
||||||
|
value = json{{"a", {{"b", 1}}}, {"z", std::move(value)}};
|
||||||
|
}
|
||||||
|
std::vector<std::uint8_t> output;
|
||||||
|
CHECK_THROWS_AS(json::to_bson(value, output), json::out_of_range&);
|
||||||
|
CHECK(output.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values nested too deeply for the call stack (#5392)")
|
||||||
|
{
|
||||||
|
// serializing recursed once per nesting level, and computed every
|
||||||
|
// nested document's length by walking everything below it again.
|
||||||
|
// The values are only parsed, serialized and walked, never copied or
|
||||||
|
// compared, since those recurse too.
|
||||||
|
const std::size_t depth = 100000;
|
||||||
|
for (const bool objects :
|
||||||
|
{
|
||||||
|
false, true
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(objects);
|
||||||
|
std::string text = "{\"a\":";
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += objects ? "{\"a\":" : "[";
|
||||||
|
}
|
||||||
|
text += "1";
|
||||||
|
text.append(depth, objects ? '}' : ']');
|
||||||
|
text += "}";
|
||||||
|
|
||||||
|
const auto bson = json::to_bson(json::parse(text));
|
||||||
|
const auto result = json::from_bson(bson);
|
||||||
|
const json* p = &result.at("a");
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
p = objects ? &p->at("a") : &p->at(0);
|
||||||
|
}
|
||||||
|
CHECK(*p == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Invalid document size handling")
|
||||||
|
{
|
||||||
|
SECTION("document size must be at least 5")
|
||||||
|
{
|
||||||
|
std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 4 does not match the number of bytes read (5)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("declared document size must match consumed bytes (extra trailing element)")
|
||||||
|
{
|
||||||
|
// Declares 5-byte empty document but appends an int32 element after the declared end.
|
||||||
|
std::vector<std::uint8_t> const v =
|
||||||
|
{
|
||||||
|
0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x10, 'a', 'd', 'm', 'i', 'n', 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 16: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (16)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("declared document size must match consumed bytes (premature terminator)")
|
||||||
|
{
|
||||||
|
// Declares 32-byte document but only contains the size field followed by an immediate terminator.
|
||||||
|
std::vector<std::uint8_t> const v =
|
||||||
|
{
|
||||||
|
0x20, 0x00, 0x00, 0x00,
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: document size 32 does not match the number of bytes read (5)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("array declared size must match consumed bytes")
|
||||||
|
{
|
||||||
|
// Outer object contains an array "a" that declares 5 bytes (empty) but
|
||||||
|
// actually contains an int32 element before its terminator.
|
||||||
|
std::vector<std::uint8_t> const v =
|
||||||
|
{
|
||||||
|
0x14, 0x00, 0x00, 0x00, // object size = 20
|
||||||
|
0x04, 'a', 0x00, // key "a", array type
|
||||||
|
0x05, 0x00, 0x00, 0x00, // array declared size = 5 (empty)
|
||||||
|
0x10, '0', 0x00, 0x01, 0x00, 0x00, 0x00, // extra int32 element "0" = 1
|
||||||
|
0x00, // array terminator
|
||||||
|
0x00 // object terminator
|
||||||
|
};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: document size 5 does not match the number of bytes read (12)", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("BSON string must end with 0x00")
|
||||||
|
{
|
||||||
|
// Length-prefixed string whose terminator byte is 'X' (0x58), not 0x00.
|
||||||
|
std::vector<std::uint8_t> const v =
|
||||||
|
{
|
||||||
|
0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 's', 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00,
|
||||||
|
'A', 'X',
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 13: syntax error while parsing BSON string: BSON string is not null-terminated", json::parse_error&);
|
||||||
|
CHECK(json::from_bson(v, true, false).is_discarded());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1833,6 +1833,59 @@ TEST_CASE("CBOR")
|
|||||||
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_cbor(std::vector<uint8_t>({0xa1, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
|
{
|
||||||
|
// a two-character text string (major type 3) whose bytes are not
|
||||||
|
// valid UTF-8 (0xC0 0xAE is an overlong encoding of '.') must be
|
||||||
|
// rejected at decode time, matching every other kind of
|
||||||
|
// malformed binary input, rather than only failing later when
|
||||||
|
// the resulting value is dumped
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0xc0, 0xae}), true, false).is_discarded());
|
||||||
|
|
||||||
|
// a CBOR byte string (major type 2) with the very same bytes is
|
||||||
|
// NOT text and must still be accepted as-is
|
||||||
|
CHECK_NOTHROW(_ = json::from_cbor(std::vector<uint8_t>({0x42, 0xc0, 0xae})));
|
||||||
|
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
||||||
|
|
||||||
|
// valid UTF-8 must still round-trip
|
||||||
|
const json j = "h\xc3\xa9llo, w\xc3\xb6rld! \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // héllo, wörld! 日本語
|
||||||
|
CHECK(json::from_cbor(json::to_cbor(j)) == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("invalid UTF-8 in indefinite-length string")
|
||||||
|
{
|
||||||
|
json _;
|
||||||
|
|
||||||
|
// every chunk must be valid UTF-8 on its own (RFC 8949, Section
|
||||||
|
// 3.2.3), so a code point split across two chunks is rejected
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x61, 0xc3, 0x61, 0xa9, 0xff}), true, false).is_discarded());
|
||||||
|
|
||||||
|
// an ill-formed later chunk is rejected after valid ones
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc0, 0xae, 0xff})), "[json.exception.parse_error.113] parse error at byte 7: syntax error while parsing CBOR string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
|
||||||
|
// valid multi-byte chunks are accepted
|
||||||
|
CHECK(json::from_cbor(std::vector<uint8_t>({0x7f, 0x62, 0xc3, 0xa9, 0x62, 0xc3, 0xb6, 0xff})) == "\xc3\xa9\xc3\xb6");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("many chunks in indefinite-length string")
|
||||||
|
{
|
||||||
|
// only the newly read chunk is validated, not the whole string
|
||||||
|
// collected so far; validating the latter made this input take
|
||||||
|
// quadratic time (about ten seconds for 100000 chunks)
|
||||||
|
constexpr std::size_t chunks = 100000;
|
||||||
|
std::vector<uint8_t> v{0x7f};
|
||||||
|
for (std::size_t i = 0; i < chunks; ++i)
|
||||||
|
{
|
||||||
|
v.push_back(0x61);
|
||||||
|
v.push_back('a');
|
||||||
|
}
|
||||||
|
v.push_back(0xff);
|
||||||
|
CHECK(json::from_cbor(v) == std::string(chunks, 'a'));
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("strict mode")
|
SECTION("strict mode")
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> const vec = {0xf6, 0xf6};
|
std::vector<uint8_t> const vec = {0xf6, 0xf6};
|
||||||
|
|||||||
@@ -11,11 +11,15 @@
|
|||||||
// capture whether JSON_STRICT_NUL_HANDLING was enabled on the command line
|
// capture whether JSON_STRICT_NUL_HANDLING was enabled on the command line
|
||||||
// (e.g. -DJSON_STRICT_NUL_HANDLING=1) *before* including json.hpp, since the
|
// (e.g. -DJSON_STRICT_NUL_HANDLING=1) *before* including json.hpp, since the
|
||||||
// library #undefs JSON_STRICT_NUL_HANDLING itself once the header has been
|
// library #undefs JSON_STRICT_NUL_HANDLING itself once the header has been
|
||||||
// fully processed (see include/nlohmann/detail/macro_unscope.hpp)
|
// fully processed unless JSON_TEST_KEEP_MACROS is defined (see
|
||||||
|
// include/nlohmann/detail/macro_unscope.hpp)
|
||||||
#if defined(JSON_STRICT_NUL_HANDLING) && (JSON_STRICT_NUL_HANDLING == 1)
|
#if defined(JSON_STRICT_NUL_HANDLING) && (JSON_STRICT_NUL_HANDLING == 1)
|
||||||
#define JSON_TEST_STRICT_NUL_HANDLING_ENABLED 1
|
#define JSON_TEST_STRICT_NUL_HANDLING_ENABLED 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define JSON_TEST_STRINGIZE_EX(x) #x
|
||||||
|
#define JSON_TEST_STRINGIZE(x) JSON_TEST_STRINGIZE_EX(x)
|
||||||
|
|
||||||
#define JSON_TESTS_PRIVATE
|
#define JSON_TESTS_PRIVATE
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
@@ -566,6 +570,16 @@ TEST_CASE("parser class")
|
|||||||
// left at its default or forced to 1 (e.g. by the dedicated
|
// left at its default or forced to 1 (e.g. by the dedicated
|
||||||
// ci_test_strict_nul_handling CI target), so only the section
|
// ci_test_strict_nul_handling CI target), so only the section
|
||||||
// matching the actual, compiled-in behavior can pass.
|
// matching the actual, compiled-in behavior can pass.
|
||||||
|
SECTION("the macro is part of the ABI tag")
|
||||||
|
{
|
||||||
|
const std::string ns = JSON_TEST_STRINGIZE(NLOHMANN_JSON_NAMESPACE);
|
||||||
|
#if defined(JSON_TEST_STRICT_NUL_HANDLING_ENABLED)
|
||||||
|
CHECK(ns.find("_snul") != std::string::npos);
|
||||||
|
#else
|
||||||
|
CHECK(ns.find("_snul") == std::string::npos);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if !defined(JSON_TEST_STRICT_NUL_HANDLING_ENABLED)
|
#if !defined(JSON_TEST_STRICT_NUL_HANDLING_ENABLED)
|
||||||
SECTION("default behavior (macro not enabled)")
|
SECTION("default behavior (macro not enabled)")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using nlohmann::json;
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
#include <valarray>
|
#include <valarray>
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
@@ -1233,6 +1234,59 @@ TEST_CASE("deserialization")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("stream position after extraction without JSON_PRECISE_STREAM_POSITION (#5340)")
|
||||||
|
{
|
||||||
|
// By default, the character that terminates a number is consumed, so
|
||||||
|
// the stream is left one byte too far after a number (and only after a
|
||||||
|
// number). JSON_PRECISE_STREAM_POSITION changes this; see
|
||||||
|
// unit-precise-stream-position.cpp. These checks pin the default.
|
||||||
|
const auto remaining = [](std::istream & is) -> std::string
|
||||||
|
{
|
||||||
|
return {std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>()};
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("the character after a number is consumed")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == 1);
|
||||||
|
CHECK(remaining(ss) == "rue");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("the character after other values is not consumed")
|
||||||
|
{
|
||||||
|
std::istringstream ss("[1]true");
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == json::parse("[1]"));
|
||||||
|
CHECK(remaining(ss) == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comma-separated numbers can be read one by one")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1,2,3");
|
||||||
|
json j1;
|
||||||
|
json j2;
|
||||||
|
json j3;
|
||||||
|
ss >> j1 >> j2 >> j3;
|
||||||
|
CHECK(j1 == 1);
|
||||||
|
CHECK(j2 == 2);
|
||||||
|
CHECK(j3 == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("std::getline after a number skips the line break")
|
||||||
|
{
|
||||||
|
std::istringstream ss("42\nfoo");
|
||||||
|
json j;
|
||||||
|
std::string line;
|
||||||
|
ss >> j;
|
||||||
|
std::getline(ss, line);
|
||||||
|
CHECK(j == 42);
|
||||||
|
CHECK(line == "foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// build with C++20
|
// build with C++20
|
||||||
// JSON_HAS_CPP_20
|
// JSON_HAS_CPP_20
|
||||||
#if defined(__cpp_char8_t)
|
#if defined(__cpp_char8_t)
|
||||||
|
|||||||
@@ -361,6 +361,106 @@ TEST_CASE("Regression tests for extended diagnostics")
|
|||||||
CHECK(p == o);
|
CHECK(p == o);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("Regression test - erase() and update() must keep JSON_DIAGNOSTICS parent pointers of ordered_json members")
|
||||||
|
{
|
||||||
|
// ordered_json keeps its members in a vector: erasing a member
|
||||||
|
// re-constructs all members after it in place, and adding a key may
|
||||||
|
// reallocate the vector; both reset the parent pointers of the members
|
||||||
|
// that were moved
|
||||||
|
using nlohmann::ordered_json;
|
||||||
|
|
||||||
|
const auto check_parents = [](const ordered_json & j)
|
||||||
|
{
|
||||||
|
// const access, so operator[] cannot repair the parent pointers
|
||||||
|
CHECK_THROWS_WITH_AS(j["z"]["x"].at(0), "[json.exception.type_error.304] (/z/x) cannot use at() with number", ordered_json::type_error);
|
||||||
|
|
||||||
|
// must not trigger assert_invariant() in a debug/assert-enabled build
|
||||||
|
ordered_json const copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(copy == j);
|
||||||
|
};
|
||||||
|
|
||||||
|
// erase(key)
|
||||||
|
{
|
||||||
|
ordered_json j = {{"a", 1}, {"z", {{"x", 1}}}};
|
||||||
|
CHECK(j.erase("a") == 1);
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// erase(iterator)
|
||||||
|
{
|
||||||
|
ordered_json j = {{"a", 1}, {"z", {{"x", 1}}}};
|
||||||
|
j.erase(j.begin());
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// erase(iterator, iterator)
|
||||||
|
{
|
||||||
|
ordered_json j = {{"a", 1}, {"b", 2}, {"z", {{"x", 1}}}};
|
||||||
|
j.erase(j.begin(), j.find("z"));
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// patch() removes via erase(iterator)
|
||||||
|
{
|
||||||
|
ordered_json j = {{"a", 1}, {"z", {{"x", 1}}}};
|
||||||
|
j.patch_inplace(ordered_json::parse(R"([{"op": "remove", "path": "/a"}])"));
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update(j)
|
||||||
|
{
|
||||||
|
ordered_json j = {{"z", {{"x", 1}}}};
|
||||||
|
j.update({{"a", 1}, {"b", 2}});
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update(j, true), the outer and the nested vector both grow
|
||||||
|
{
|
||||||
|
ordered_json j = {{"z", {{"x", 1}}}};
|
||||||
|
j.update({{"z", {{"y", 2}}}, {"a", 1}}, true);
|
||||||
|
check_parents(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update(j, true) around its descent bound, where the nested vectors
|
||||||
|
// grow while the objects are merged without recursing
|
||||||
|
for (const std::size_t depth :
|
||||||
|
{
|
||||||
|
nlohmann::detail::recursion_depth_limit() - 1, nlohmann::detail::recursion_depth_limit(), nlohmann::detail::recursion_depth_limit() + 2
|
||||||
|
})
|
||||||
|
{
|
||||||
|
ordered_json j = {{"z", {{"x", 1}}}};
|
||||||
|
ordered_json patch = {{"a", 1}, {"b", 2}, {"c", {{"d", 3}}}};
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
j = ordered_json{{"k", 0}, {"n", std::move(j)}};
|
||||||
|
patch = ordered_json{{"n", std::move(patch)}, {"l", 1}, {"m", 2}};
|
||||||
|
}
|
||||||
|
j.update(patch, true);
|
||||||
|
|
||||||
|
// must not trigger assert_invariant() on any level in a
|
||||||
|
// debug/assert-enabled build
|
||||||
|
ordered_json const copy = j; // NOLINT(performance-unnecessary-copy-initialization)
|
||||||
|
CHECK(copy == j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge_patch() inserts "c" and removes "d" at /a/c, then inserts "e"
|
||||||
|
// at /a, which copies /a/c
|
||||||
|
{
|
||||||
|
auto j = ordered_json::parse(R"({"a": {"c": {"d": {}}}})");
|
||||||
|
j.merge_patch(ordered_json::parse(R"({"a": {"c": {"c": "s", "d": null}, "e": "s"}})"));
|
||||||
|
CHECK(j.dump() == R"({"a":{"c":{"c":"s"},"e":"s"}})");
|
||||||
|
|
||||||
|
auto const& constJ = j;
|
||||||
|
#if JSON_DIAGNOSTIC_POSITIONS
|
||||||
|
CHECK_THROWS_WITH_AS(constJ["a"]["c"]["c"].at(0), "[json.exception.type_error.304] (/a/c/c) (bytes 18-21) cannot use at() with string", ordered_json::type_error);
|
||||||
|
#else
|
||||||
|
CHECK_THROWS_WITH_AS(constJ["a"]["c"]["c"].at(0), "[json.exception.type_error.304] (/a/c/c) cannot use at() with string", ordered_json::type_error);
|
||||||
|
#endif
|
||||||
|
ordered_json const copy = j;
|
||||||
|
CHECK(copy == j);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
TEST_CASE("Better diagnostics past the descent bound of update() and merge_patch()")
|
||||||
|
|||||||
@@ -157,6 +157,54 @@ TEST_CASE("tests on deeply nested JSONs")
|
|||||||
CHECK(deep_depth == depth);
|
CHECK(deep_depth == depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("comparing")
|
||||||
|
{
|
||||||
|
// Comparing used to descend once per level, and an ordered
|
||||||
|
// comparison used to compare every pair of elements twice, once in
|
||||||
|
// each direction, which took exponentially long in the nesting
|
||||||
|
// depth. Both are gone: these finish in milliseconds, where the
|
||||||
|
// second used to take longer than anyone would wait even for a
|
||||||
|
// value nested only a few dozen levels deep.
|
||||||
|
const std::string text = std::string(depth, '[') + '0' + std::string(depth, ']');
|
||||||
|
const json j = json::parse(text);
|
||||||
|
const json same = json::parse(text);
|
||||||
|
const json larger = json::parse(std::string(depth, '[') + '1' + std::string(depth, ']'));
|
||||||
|
|
||||||
|
CHECK(j == same);
|
||||||
|
CHECK_FALSE(j == larger);
|
||||||
|
CHECK(j != larger);
|
||||||
|
|
||||||
|
CHECK(j < larger);
|
||||||
|
CHECK_FALSE(larger < j);
|
||||||
|
CHECK(larger > j);
|
||||||
|
CHECK(j <= same);
|
||||||
|
CHECK(j >= same);
|
||||||
|
|
||||||
|
// a value that ends earlier is the smaller one
|
||||||
|
const json shorter = json::parse(std::string(depth - 1, '[') + '0' + std::string(depth - 1, ']'));
|
||||||
|
CHECK_FALSE(j == shorter);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("comparing objects")
|
||||||
|
{
|
||||||
|
std::string text;
|
||||||
|
text.reserve((6 * depth) + 1);
|
||||||
|
for (std::size_t i = 0; i < depth; ++i)
|
||||||
|
{
|
||||||
|
text += "{\"a\":";
|
||||||
|
}
|
||||||
|
text += '1';
|
||||||
|
text.append(depth, '}');
|
||||||
|
|
||||||
|
const json j = json::parse(text);
|
||||||
|
const json same = json::parse(text);
|
||||||
|
|
||||||
|
CHECK(j == same);
|
||||||
|
CHECK_FALSE(j != same);
|
||||||
|
CHECK(j <= same);
|
||||||
|
CHECK(j >= same);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("the copy is independent of the original")
|
SECTION("the copy is independent of the original")
|
||||||
{
|
{
|
||||||
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
const json j = json::parse(std::string(depth, '[') + '0' + std::string(depth, ']'));
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ std::string nested_objects(const std::size_t depth, const int variant)
|
|||||||
}
|
}
|
||||||
text += "\"a\":";
|
text += "\"a\":";
|
||||||
}
|
}
|
||||||
text += variant == 1 ? "{\"x\":1,\"y\":null}" : "{\"y\":2}";
|
text += variant == 1 ? R"({"x":1,"y":null})" : "{\"y\":2}";
|
||||||
text.append(depth, '}');
|
text.append(depth, '}');
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ std::string nested_objects(const std::size_t depth, const int variant)
|
|||||||
if (variant == 2 && i % 5 == 0)
|
if (variant == 2 && i % 5 == 0)
|
||||||
{
|
{
|
||||||
// an object replacing a primitive, which is not merged
|
// an object replacing a primitive, which is not merged
|
||||||
text += "\"s0\":{\"o\":1},";
|
text += R"("s0":{"o":1},)";
|
||||||
}
|
}
|
||||||
text += "\"a\":";
|
text += "\"a\":";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1554,6 +1554,27 @@ TEST_CASE("MessagePack")
|
|||||||
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
CHECK(json::from_msgpack(std::vector<uint8_t>({0x81, 0xff, 0x01}), true, false).is_discarded());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("invalid UTF-8 in string (see #5529)")
|
||||||
|
{
|
||||||
|
// a fixstr of length 2 (0xA0 | 2) whose bytes are not valid UTF-8
|
||||||
|
// (0xC0 0xAE is an overlong encoding of '.') must be rejected at
|
||||||
|
// decode time, matching every other kind of malformed binary
|
||||||
|
// input, rather than only failing later when the resulting
|
||||||
|
// value is dumped
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae})), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing MessagePack string: invalid string: ill-formed UTF-8 byte", json::parse_error&);
|
||||||
|
CHECK(json::from_msgpack(std::vector<uint8_t>({0xa2, 0xc0, 0xae}), true, false).is_discarded());
|
||||||
|
|
||||||
|
// a MessagePack bin8 blob with the very same bytes is NOT text
|
||||||
|
// and must still be accepted as-is
|
||||||
|
CHECK_NOTHROW(_ = json::from_msgpack(std::vector<uint8_t>({0xc4, 0x02, 0xc0, 0xae})));
|
||||||
|
CHECK(_ == json::binary(std::vector<std::uint8_t>({0xc0, 0xae})));
|
||||||
|
|
||||||
|
// valid UTF-8 must still round-trip
|
||||||
|
const json j = "h\xc3\xa9llo, w\xc3\xb6rld! \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; // héllo, wörld! 日本語
|
||||||
|
CHECK(json::from_msgpack(json::to_msgpack(j)) == j);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("strict mode")
|
SECTION("strict mode")
|
||||||
{
|
{
|
||||||
std::vector<uint8_t> const vec = {0xc0, 0xc0};
|
std::vector<uint8_t> const vec = {0xc0, 0xc0};
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
// __ _____ _____ _____
|
||||||
|
// __| | __| | | | 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_PRECISE_STREAM_POSITION, so it defines the
|
||||||
|
// macro itself rather than relying on a -D flag, and runs in every build. The
|
||||||
|
// default behavior is pinned in unit-deserialization.cpp.
|
||||||
|
#ifdef JSON_PRECISE_STREAM_POSITION
|
||||||
|
#undef JSON_PRECISE_STREAM_POSITION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JSON_PRECISE_STREAM_POSITION 1
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
using nlohmann::json;
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <sstream>
|
||||||
|
#include <streambuf>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define STRINGIZE_EX(x) #x
|
||||||
|
#define STRINGIZE(x) STRINGIZE_EX(x)
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// A streambuf that keeps no get area at all and refuses every putback: with an
|
||||||
|
// empty get area, sungetc() always ends up in pbackfail(). Used to check that
|
||||||
|
// the character terminating a number is left in the input without relying on
|
||||||
|
// the streambuf being able to put a consumed character back.
|
||||||
|
class no_putback_streambuf : public std::streambuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit no_putback_streambuf(std::string s) : m_data(std::move(s)) {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// peek at the next character without consuming it
|
||||||
|
int_type underflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume the next character
|
||||||
|
int_type uflow() override
|
||||||
|
{
|
||||||
|
if (m_pos >= m_data.size())
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
return traits_type::to_int_type(m_data[m_pos++]);
|
||||||
|
}
|
||||||
|
|
||||||
|
int_type pbackfail(int_type /*c*/) override
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string m_data;
|
||||||
|
std::size_t m_pos = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// read the characters that are left in a stream
|
||||||
|
std::string remaining(std::istream& is)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
char c = 0;
|
||||||
|
while (is.get(c))
|
||||||
|
{
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("JSON_PRECISE_STREAM_POSITION")
|
||||||
|
{
|
||||||
|
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_diag_psp
|
||||||
|
CHECK(ns.find("_psp") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a number does not consume the character that terminates it")
|
||||||
|
{
|
||||||
|
// a number is only terminated by the character following it; that
|
||||||
|
// character must be given back so the stream is positioned right
|
||||||
|
// after the value
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"1true", "true"},
|
||||||
|
{"1[2]", "[2]"},
|
||||||
|
{"1{}", "{}"},
|
||||||
|
{R"(1"a")", R"("a")"},
|
||||||
|
{"1 true", " true"},
|
||||||
|
{"12,", ","},
|
||||||
|
{"-0.5e3x", "x"},
|
||||||
|
{"1null", "null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == json::parse(test.first.substr(0, test.first.size() - test.second.size())));
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("values that are self-delimiting are unaffected")
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<std::string, std::string>> tests =
|
||||||
|
{
|
||||||
|
{"truefalse", "false"},
|
||||||
|
{"[1][2]", "[2]"},
|
||||||
|
{R"({"a":1}{"b":2})", R"({"b":2})"},
|
||||||
|
{R"("a""b")", R"("b")"},
|
||||||
|
{"null null", " null"}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& test : tests)
|
||||||
|
{
|
||||||
|
CAPTURE(test.first);
|
||||||
|
std::istringstream ss(test.first);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss) == test.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a number at the end of the input leaves nothing behind")
|
||||||
|
{
|
||||||
|
for (const std::string s :
|
||||||
|
{"1", "12", "-3.5e2", " 7 "
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CAPTURE(s);
|
||||||
|
std::istringstream ss(s);
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(remaining(ss).find_first_not_of(" \t\n\r") == std::string::npos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("repeated extraction of concatenated values")
|
||||||
|
{
|
||||||
|
std::istringstream ss(R"(1true[2]3"x"{"a":4}5)");
|
||||||
|
const std::vector<json> expected =
|
||||||
|
{
|
||||||
|
json(1), json(true), json::parse("[2]"), json(3),
|
||||||
|
json("x"), json::parse(R"({"a":4})"), json(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& e : expected)
|
||||||
|
{
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("differences to the default behavior")
|
||||||
|
{
|
||||||
|
// both of these work by accident without the macro, because the
|
||||||
|
// character after a number is swallowed; see unit-deserialization.cpp
|
||||||
|
|
||||||
|
SECTION("a separator after a number is not skipped")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1,2");
|
||||||
|
json j;
|
||||||
|
ss >> j;
|
||||||
|
CHECK(j == 1);
|
||||||
|
CHECK_THROWS_AS(ss >> j, json::parse_error&);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("std::getline after a number sees the line break")
|
||||||
|
{
|
||||||
|
std::istringstream ss("42\nfoo");
|
||||||
|
json j;
|
||||||
|
std::string line;
|
||||||
|
ss >> j;
|
||||||
|
std::getline(ss, line);
|
||||||
|
CHECK(j == 42);
|
||||||
|
CHECK(line.empty());
|
||||||
|
std::getline(ss, line);
|
||||||
|
CHECK(line == "foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("sax_parse with strict == false")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
json j;
|
||||||
|
nlohmann::detail::json_sax_dom_parser<json, nlohmann::detail::input_stream_adapter> sdp(j, true);
|
||||||
|
CHECK(json::sax_parse(ss, &sdp, nlohmann::detail::input_format_t::json, false));
|
||||||
|
CHECK(j == 1);
|
||||||
|
CHECK(remaining(ss) == "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("strict parsing still rejects trailing data")
|
||||||
|
{
|
||||||
|
std::istringstream ss("1true");
|
||||||
|
json _;
|
||||||
|
CHECK_THROWS_WITH_AS(_ = json::parse(ss),
|
||||||
|
"[json.exception.parse_error.101] parse error at line 1, column 5: syntax error while parsing value - unexpected true literal; expected end of input", json::parse_error&);
|
||||||
|
|
||||||
|
std::istringstream ss2("1true");
|
||||||
|
CHECK_FALSE(json::accept(ss2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("a streambuf that cannot put back is not needed")
|
||||||
|
{
|
||||||
|
// the terminating character is never consumed, so no putback
|
||||||
|
// position is required
|
||||||
|
no_putback_streambuf buf("1true");
|
||||||
|
std::istream is(&buf);
|
||||||
|
json j;
|
||||||
|
is >> j;
|
||||||
|
CHECK(j == json(1));
|
||||||
|
CHECK(remaining(is) == "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,6 +94,16 @@ TEST_CASE("serialization")
|
|||||||
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
|
CHECK(j.dump(-1, ' ', true, json::error_handler_t::replace) == "\"\\u00e4\\ufffd\\u00fc\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SECTION("invalid character (regression guard for shared UTF-8 decoder, see #5529)")
|
||||||
|
{
|
||||||
|
// dump_escaped_impl() now calls the UTF-8 decoder shared with the
|
||||||
|
// binary readers (detail::decode() in string_utils.hpp) instead
|
||||||
|
// of a private copy; the exact type_error.316 message/behavior
|
||||||
|
// must stay byte-for-byte the same as before that extraction
|
||||||
|
const json j = "ä\xA9ü";
|
||||||
|
CHECK_THROWS_WITH_AS(utils::ignore_return_value(j.dump()), "[json.exception.type_error.316] invalid UTF-8 byte at index 2: 0xA9", json::type_error&);
|
||||||
|
}
|
||||||
|
|
||||||
SECTION("ending with incomplete character")
|
SECTION("ending with incomplete character")
|
||||||
{
|
{
|
||||||
const json j = "123\xC2";
|
const json j = "123\xC2";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using nlohmann::json;
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "make_test_data_available.hpp"
|
#include "make_test_data_available.hpp"
|
||||||
|
#include "round_trip_corpus.hpp"
|
||||||
#include "test_utils.hpp"
|
#include "test_utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -2265,7 +2266,9 @@ TEST_CASE("UBJSON optimized arrays of a valueless type are bounded")
|
|||||||
|
|
||||||
SECTION("an excessive count is rejected")
|
SECTION("an excessive count is rejected")
|
||||||
{
|
{
|
||||||
// 'l' is a big-endian int32: 0x7FFFFFFF elements, about 34 GB of value
|
// 'l' is a big-endian int32: 0x7FFFFFFF elements, about 34 GB of value;
|
||||||
|
// OSS-Fuzz reported this shape as a parse_ubjson_fuzzer timeout
|
||||||
|
// (testcase 6347769435193344, no issue filed)
|
||||||
for (const auto marker :
|
for (const auto marker :
|
||||||
{'Z', 'T', 'F'
|
{'Z', 'T', 'F'
|
||||||
})
|
})
|
||||||
@@ -2817,6 +2820,51 @@ TEST_CASE("UBJSON use_type requires use_size")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UBJSON round-trip invariants")
|
||||||
|
{
|
||||||
|
// This checks what the parse_ubjson_fuzzer driver checks (see
|
||||||
|
// tests/src/fuzzer-parse_ubjson.cpp), so that a regression shows up in CI
|
||||||
|
// rather than as an OSS-Fuzz report: every value from_ubjson() 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
|
||||||
|
// reproduces the exact bytes. Beyond the driver, this also checks that j2
|
||||||
|
// equals j1. Values are compared with dump() rather than operator==,
|
||||||
|
// because a NaN never compares equal to itself.
|
||||||
|
struct options
|
||||||
|
{
|
||||||
|
bool use_size;
|
||||||
|
bool use_type;
|
||||||
|
};
|
||||||
|
const std::vector<options> all_options =
|
||||||
|
{
|
||||||
|
{false, false},
|
||||||
|
{true, false},
|
||||||
|
{true, true},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& j0 : utils::round_trip_corpus::values())
|
||||||
|
{
|
||||||
|
// turn the corpus value into a value as from_ubjson() returns it; this
|
||||||
|
// has no binary values, as UBJSON writes them as arrays of integers
|
||||||
|
for (const auto& initial : all_options)
|
||||||
|
{
|
||||||
|
const json j1 = json::from_ubjson(json::to_ubjson(j0, initial.use_size, initial.use_type));
|
||||||
|
|
||||||
|
for (const auto& o : all_options)
|
||||||
|
{
|
||||||
|
INFO("j1 = " << j1.dump() << ", use_size = " << o.use_size << ", use_type = " << o.use_type);
|
||||||
|
|
||||||
|
const std::vector<std::uint8_t> vec = json::to_ubjson(j1, o.use_size, o.use_type);
|
||||||
|
json j2;
|
||||||
|
// anything the library writes must be parsable by the library
|
||||||
|
REQUIRE_NOTHROW(j2 = json::from_ubjson(vec));
|
||||||
|
CHECK(j2.dump() == j1.dump());
|
||||||
|
CHECK(json::to_ubjson(j2, o.use_size, o.use_type) == vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
||||||
{
|
{
|
||||||
SECTION("input from self-generated UBJSON files")
|
SECTION("input from self-generated UBJSON files")
|
||||||
@@ -2933,3 +2981,18 @@ TEST_CASE("UBJSON roundtrips" * doctest::skip())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("UBJSON optimized array of unsigned integers beyond int64")
|
||||||
|
{
|
||||||
|
// UBJSON has no unsigned 64-bit type, so such values are written as
|
||||||
|
// high-precision numbers - also as the type of an optimized container
|
||||||
|
const json j = {18446744073709551615ULL, 9223372036854775808ULL};
|
||||||
|
const std::vector<std::uint8_t> expected =
|
||||||
|
{
|
||||||
|
'[', '$', 'H', '#', 'i', 2,
|
||||||
|
'i', 20, '1', '8', '4', '4', '6', '7', '4', '4', '0', '7', '3', '7', '0', '9', '5', '5', '1', '6', '1', '5',
|
||||||
|
'i', 19, '9', '2', '2', '3', '3', '7', '2', '0', '3', '6', '8', '5', '4', '7', '7', '5', '8', '0', '8'
|
||||||
|
};
|
||||||
|
CHECK(json::to_ubjson(j, true, true) == expected);
|
||||||
|
CHECK(json::from_ubjson(expected) == j);
|
||||||
|
}
|
||||||
|
|||||||
@@ -778,6 +778,193 @@ class derived_person_only_serialize_private_3 : person_without_default_construct
|
|||||||
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(derived_person_only_serialize_private_3, person_without_default_constructor_3, "json_hair_color", hair_color)
|
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE_WITH_NAMES(derived_person_only_serialize_private_3, person_without_default_constructor_3, "json_hair_color", hair_color)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Zero-member types for issue #4041: NLOHMANN_DEFINE_TYPE_* and
|
||||||
|
// NLOHMANN_DEFINE_DERIVED_TYPE_* must compile and produce a valid (empty)
|
||||||
|
// JSON object when no member arguments are given.
|
||||||
|
class empty_intrusive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator==(const empty_intrusive& /*rhs*/) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(empty_intrusive)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_intrusive_with_default
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator==(const empty_intrusive_with_default& /*rhs*/) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(empty_intrusive_with_default)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_intrusive_only_serialize
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(empty_intrusive_only_serialize)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_non_intrusive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator==(const empty_non_intrusive& /*rhs*/) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(empty_non_intrusive)
|
||||||
|
|
||||||
|
class empty_non_intrusive_with_default
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
bool operator==(const empty_non_intrusive_with_default& /*rhs*/) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(empty_non_intrusive_with_default)
|
||||||
|
|
||||||
|
class empty_non_intrusive_only_serialize {};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(empty_non_intrusive_only_serialize)
|
||||||
|
|
||||||
|
class empty_derived_intrusive : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_intrusive() = default;
|
||||||
|
empty_derived_intrusive(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(empty_derived_intrusive, person_with_private_data)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_derived_intrusive_with_default : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_intrusive_with_default() = default;
|
||||||
|
empty_derived_intrusive_with_default(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(empty_derived_intrusive_with_default, person_with_private_data)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_derived_intrusive_only_serialize : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_intrusive_only_serialize() = default;
|
||||||
|
empty_derived_intrusive_only_serialize(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(empty_derived_intrusive_only_serialize, person_with_private_data)
|
||||||
|
};
|
||||||
|
|
||||||
|
class empty_derived_non_intrusive : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_non_intrusive() = default;
|
||||||
|
empty_derived_non_intrusive(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(empty_derived_non_intrusive, person_with_private_data)
|
||||||
|
|
||||||
|
class empty_derived_non_intrusive_with_default : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_non_intrusive_with_default() = default;
|
||||||
|
empty_derived_non_intrusive_with_default(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(empty_derived_non_intrusive_with_default, person_with_private_data)
|
||||||
|
|
||||||
|
class empty_derived_non_intrusive_only_serialize : public person_with_private_data
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
empty_derived_non_intrusive_only_serialize() = default;
|
||||||
|
empty_derived_non_intrusive_only_serialize(std::string name_, int age_, json metadata_)
|
||||||
|
: person_with_private_data(std::move(name_), age_, std::move(metadata_))
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(empty_derived_non_intrusive_only_serialize, person_with_private_data)
|
||||||
|
|
||||||
|
// Types at the documented maximum member count (63) for issue #4041's
|
||||||
|
// argument-count dispatch. The derived-type macros carry a two-token
|
||||||
|
// Type,BaseType prefix, so they reach two slots further into
|
||||||
|
// NLOHMANN_JSON_GET_MACRO than the non-derived ones and are the first to break
|
||||||
|
// if the tag dispatch runs out of positional slots.
|
||||||
|
class max_members
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int m1{}, m2{}, m3{}, m4{}, m5{}, m6{}, m7{}, m8{}, m9{}, m10{}, m11{}, m12{}, m13{}, m14{}, m15{}, m16{}, m17{}, m18{}, m19{}, m20{}, m21{}, m22{}, m23{}, m24{}, m25{}, m26{}, m27{}, m28{}, m29{}, m30{}, m31{}, m32{}, m33{}, m34{}, m35{}, m36{}, m37{}, m38{}, m39{}, m40{}, m41{}, m42{}, m43{}, m44{}, m45{}, m46{}, m47{}, m48{}, m49{}, m50{}, m51{}, m52{}, m53{}, m54{}, m55{}, m56{}, m57{}, m58{}, m59{}, m60{}, m61{}, m62{}, m63{};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(max_members, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22, m23, m24, m25, m26, m27, m28, m29, m30, m31, m32, m33, m34, m35, m36, m37, m38, m39, m40, m41, m42, m43, m44, m45, m46, m47, m48, m49, m50, m51, m52, m53, m54, m55, m56, m57, m58, m59, m60, m61, m62, m63)
|
||||||
|
};
|
||||||
|
|
||||||
|
class max_members_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int base_value = 0;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(max_members_base, base_value)
|
||||||
|
};
|
||||||
|
|
||||||
|
class max_members_derived : public max_members_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int m1{}, m2{}, m3{}, m4{}, m5{}, m6{}, m7{}, m8{}, m9{}, m10{}, m11{}, m12{}, m13{}, m14{}, m15{}, m16{}, m17{}, m18{}, m19{}, m20{}, m21{}, m22{}, m23{}, m24{}, m25{}, m26{}, m27{}, m28{}, m29{}, m30{}, m31{}, m32{}, m33{}, m34{}, m35{}, m36{}, m37{}, m38{}, m39{}, m40{}, m41{}, m42{}, m43{}, m44{}, m45{}, m46{}, m47{}, m48{}, m49{}, m50{}, m51{}, m52{}, m53{}, m54{}, m55{}, m56{}, m57{}, m58{}, m59{}, m60{}, m61{}, m62{}, m63{};
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(max_members_derived, max_members_base, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20, m21, m22, m23, m24, m25, m26, m27, m28, m29, m30, m31, m32, m33, m34, m35, m36, m37, m38, m39, m40, m41, m42, m43, m44, m45, m46, m47, m48, m49, m50, m51, m52, m53, m54, m55, m56, m57, m58, m59, m60, m61, m62, m63)
|
||||||
|
};
|
||||||
|
|
||||||
|
// User macros named like the dispatch suffixes (EMPTY is a common empty-macro
|
||||||
|
// idiom) must not leak into the NLOHMANN_DEFINE_TYPE_* dispatch.
|
||||||
|
#define EMPTY
|
||||||
|
#define MEMBERS clobbered_by_user_macro
|
||||||
|
|
||||||
|
class dispatch_with_user_macros_empty
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(dispatch_with_user_macros_empty)
|
||||||
|
};
|
||||||
|
|
||||||
|
class dispatch_with_user_macros_members
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
NLOHMANN_DEFINE_TYPE_INTRUSIVE(dispatch_with_user_macros_members, value)
|
||||||
|
};
|
||||||
|
|
||||||
|
class dispatch_with_user_macros_derived_empty : public dispatch_with_user_macros_members
|
||||||
|
{
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(dispatch_with_user_macros_derived_empty, dispatch_with_user_macros_members)
|
||||||
|
|
||||||
|
class dispatch_with_user_macros_derived_members : public dispatch_with_user_macros_members
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int own = 0;
|
||||||
|
};
|
||||||
|
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||||
|
NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(dispatch_with_user_macros_derived_members, dispatch_with_user_macros_members, own)
|
||||||
|
|
||||||
|
// testing for the macros also keeps -Wunused-macros from rejecting them
|
||||||
|
#if !defined(EMPTY) || !defined(MEMBERS)
|
||||||
|
#error "EMPTY and MEMBERS must stay defined for the tests above"
|
||||||
|
#endif
|
||||||
|
#undef EMPTY
|
||||||
|
#undef MEMBERS
|
||||||
|
|
||||||
} // namespace persons
|
} // namespace persons
|
||||||
|
|
||||||
TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
TEST_CASE_TEMPLATE("Serialization/deserialization via NLOHMANN_DEFINE_TYPE_INTRUSIVE and NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE", Pair, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
@@ -1191,3 +1378,183 @@ TEST_CASE_TEMPLATE("Serialization of non-default-constructible classes via NLOHM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression tests for issue #4041: NLOHMANN_DEFINE_TYPE_* and
|
||||||
|
// NLOHMANN_DEFINE_DERIVED_TYPE_* macros must compile and produce valid
|
||||||
|
// (empty, or base-only for the derived case) JSON objects when no member
|
||||||
|
// arguments are given, on every supported C++ standard.
|
||||||
|
TEST_CASE_TEMPLATE("Serialization/deserialization of zero-member types via NLOHMANN_DEFINE_TYPE_* (issue #4041)", Json, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
|
nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
constexpr bool is_ordered = std::is_same<Json, nlohmann::ordered_json>::value;
|
||||||
|
const char* const derived_dump = is_ordered
|
||||||
|
? R"({"age":1,"name":"Erik","metadata":null})"
|
||||||
|
: R"({"age":1,"metadata":null,"name":"Erik"})";
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE with zero members")
|
||||||
|
{
|
||||||
|
persons::empty_intrusive obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
CHECK(j.template get<persons::empty_intrusive>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT with zero members")
|
||||||
|
{
|
||||||
|
persons::empty_intrusive_with_default obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
CHECK(j.template get<persons::empty_intrusive_with_default>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE with zero members")
|
||||||
|
{
|
||||||
|
const persons::empty_intrusive_only_serialize obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE with zero members")
|
||||||
|
{
|
||||||
|
persons::empty_non_intrusive obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
CHECK(j.template get<persons::empty_non_intrusive>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT with zero members")
|
||||||
|
{
|
||||||
|
persons::empty_non_intrusive_with_default obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
CHECK(j.template get<persons::empty_non_intrusive_with_default>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE with zero members")
|
||||||
|
{
|
||||||
|
const persons::empty_non_intrusive_only_serialize obj{};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE with zero own members")
|
||||||
|
{
|
||||||
|
persons::empty_derived_intrusive obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
CHECK(j.template get<persons::empty_derived_intrusive>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT with zero own members")
|
||||||
|
{
|
||||||
|
persons::empty_derived_intrusive_with_default obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
CHECK(j.template get<persons::empty_derived_intrusive_with_default>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE with zero own members")
|
||||||
|
{
|
||||||
|
const persons::empty_derived_intrusive_only_serialize obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE with zero own members")
|
||||||
|
{
|
||||||
|
persons::empty_derived_non_intrusive obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
CHECK(j.template get<persons::empty_derived_non_intrusive>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT with zero own members")
|
||||||
|
{
|
||||||
|
persons::empty_derived_non_intrusive_with_default obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
CHECK(j.template get<persons::empty_derived_non_intrusive_with_default>() == obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE with zero own members")
|
||||||
|
{
|
||||||
|
const persons::empty_derived_non_intrusive_only_serialize obj{"Erik", 1, nullptr};
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.dump() == derived_dump);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test for the argument-count dispatch added for issue #4041: the
|
||||||
|
// documented maximum of 63 members must keep working, including for the
|
||||||
|
// derived-type macros whose Type,BaseType prefix consumes two dispatch slots.
|
||||||
|
TEST_CASE_TEMPLATE("Serialization/deserialization of maximum-member-count types via NLOHMANN_DEFINE_TYPE_*", Json, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
|
nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
SECTION("NLOHMANN_DEFINE_TYPE_INTRUSIVE with 63 members")
|
||||||
|
{
|
||||||
|
persons::max_members obj{};
|
||||||
|
obj.m1 = 1;
|
||||||
|
obj.m63 = 63;
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.size() == 63);
|
||||||
|
const auto obj2 = j.template get<persons::max_members>();
|
||||||
|
CHECK(obj2.m1 == 1);
|
||||||
|
CHECK(obj2.m63 == 63);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE with 63 own members")
|
||||||
|
{
|
||||||
|
persons::max_members_derived obj{};
|
||||||
|
obj.base_value = 7;
|
||||||
|
obj.m1 = 1;
|
||||||
|
obj.m63 = 63;
|
||||||
|
Json j = obj;
|
||||||
|
CHECK(j.size() == 64);
|
||||||
|
const auto obj2 = j.template get<persons::max_members_derived>();
|
||||||
|
CHECK(obj2.base_value == 7);
|
||||||
|
CHECK(obj2.m1 == 1);
|
||||||
|
CHECK(obj2.m63 == 63);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_TEMPLATE("NLOHMANN_DEFINE_TYPE_* dispatch is unaffected by user macros named EMPTY or MEMBERS", Json, // NOLINT(readability-math-missing-parentheses, bugprone-throwing-static-initialization)
|
||||||
|
nlohmann::json, nlohmann::ordered_json)
|
||||||
|
{
|
||||||
|
SECTION("zero members")
|
||||||
|
{
|
||||||
|
const persons::dispatch_with_user_macros_empty obj{};
|
||||||
|
const Json j = obj;
|
||||||
|
CHECK(j == Json::object());
|
||||||
|
CHECK_NOTHROW(j.template get<persons::dispatch_with_user_macros_empty>());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("one member")
|
||||||
|
{
|
||||||
|
persons::dispatch_with_user_macros_members obj{};
|
||||||
|
obj.value = 42;
|
||||||
|
const Json j = obj;
|
||||||
|
CHECK(j == Json({{"value", 42}}));
|
||||||
|
CHECK(j.template get<persons::dispatch_with_user_macros_members>().value == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("derived with zero own members")
|
||||||
|
{
|
||||||
|
persons::dispatch_with_user_macros_derived_empty obj{};
|
||||||
|
obj.value = 42;
|
||||||
|
const Json j = obj;
|
||||||
|
CHECK(j == Json({{"value", 42}}));
|
||||||
|
CHECK(j.template get<persons::dispatch_with_user_macros_derived_empty>().value == 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("derived with own members")
|
||||||
|
{
|
||||||
|
persons::dispatch_with_user_macros_derived_members obj{};
|
||||||
|
obj.value = 42;
|
||||||
|
obj.own = 7;
|
||||||
|
const Json j = obj;
|
||||||
|
CHECK(j == Json({{"value", 42}, {"own", 7}}));
|
||||||
|
const auto obj2 = j.template get<persons::dispatch_with_user_macros_derived_members>();
|
||||||
|
CHECK(obj2.value == 42);
|
||||||
|
CHECK(obj2.own == 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
namespaces = ['nlohmann']
|
namespaces = ['nlohmann']
|
||||||
abi_prefix = 'json_abi'
|
abi_prefix = 'json_abi'
|
||||||
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics']
|
abi_tags = ['_diag', '_ldvcmp', '_dp', '_bics', '_psp', '_snul']
|
||||||
version = '_v' + args.version.replace('.', '_')
|
version = '_v' + args.version.replace('.', '_')
|
||||||
inline_namespaces = []
|
inline_namespaces = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user